Java当中关于类设计

1 接口

定义 : 是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么 接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法 (JDK 9);它与定义类方式相似, 但是使用interface 关键字, 它也会被编译成class 文件, 但一定要明确它不是一个类, 而是一种引用数据类型; 接口的使用, 它不能创建对象, 但是可以被实现(implements , 类似于被继承); 一个实现接口的类(可以看做是接口的子类), 需要实现接口中所有的抽象方法, 创建该类的对象, 就可以调用方法了, 否则它必须是一个抽象类

引用数据类型 : 数组, 类, 接口

定义格式 :

public interface 接口名称 {
    // 抽象方法
    // 默认方法
    // 静态方法
    // 私有方法
}

含有抽象方法

public interface InterFaceName{
    public abstract void method();
}

含有默认方法和静态方法

默认方法 : 使用default修饰, default不可省略, 供子类调用或者子类重写

静态方法 : 使用static修饰, 供接口直接调用

public interface InterFaceName{
    public default void method(){
        // 执行语句
    }
    
    public static void method(){
        // 执行语句
    }
}

含有私有方法

public interface InterFaceName{
    private void method(){
        // 执行语句
    }
}

基本实现

类与接口的关系为实现关系, 即类实现接口, 该类可以称为接口的实现类, 也可称为接口的子类; 实现的动作类似基础 , 格式相仿, 只是关键字不同, 实现使用 implements 关键字

非抽象子类实现接口

  1. 必须重写接口中所有抽象方法
  2. 继承了接口的默认方法, 即可以直接调用, 也可以重写
class 类名 implements 接口名{
    // 重写接口中抽象方法[必须]
    // 重写接口中默人方法[可选]
}

// 抽象方法的使用

// 定义接口
public interface LiveAble {
    // 定义抽象方法
    public abstract void eat();
    public abstract void sleep();
}


// 定义实现类
public class Animal implements LiveAble {
    @Override
    public void eat() {
        System.out.println("吃东西");
    }
    
    @Override
    public void sleep() {
        System.out.println("晚上睡");
    }
}


// 实现测试类
public class InterfaceDemo {
    public static void main(String[] args) {
        // 创建子类对象
        Animal a = new Animal();
        // 调用实现后的方法
        a.eat();
        a.sleep();
    }
}

// 默认方法的使用

可以继承, 可以重写, 二选一, 但是只能通过实现类的对象来调用

// 定义接口
public interface LiveAble{
    public default void fly(){
        System.out.println("天上飞");
    }
}


// 定义实现类
public class Animal implements LiveAble{
    // 继承什么都不写, 直接调用
}


// 定义测试类
public class InterfaceDemo{
    public static void main(String[] args){
        // 创建子类对象
        Animal a = new Animal();
        // 调用默认方法
        a.fly();
    }
}

// 重写默认方法

// 定义接口
public interface LiveAble{
    public default void fly(){
        System.out.println("天上飞");
    }
}


// 定义实现类
public class Animal implements LiveAble{
    @Overridepublic
    public default void fly(){
        System.out.println("自由自在地飞");
    }
}


// 定义测试类
public class InterfaceDemo{
    public static void main(String[] args){
         // 创建子类对象
        Animal a = new Animal();
        // 调用默认方法
        a.fly();
    }
}

// 静态方法的使用

静态与 .class 文件相关, 只能使用接口名调用,不可以通过实现类名或者类的对象调用

// 定义接口
public interface LiveAble{
    public static void run(){
        System.out.println("跑起来!!!");
    }
}


// 定义实现类
public class Animal implements LiveAble{
    // 无法重写静态方法
}



// 定义测试类
public class InterfaceDemo{
    public static void main(String[] args){
        Animal.run() // 错误,无法继承,也无法调用
        LiveAble.run();
    }
}

// 私有方法的使用

私有方法 : 只有默认方法可以调用

私有静态方法 : 默认方法和静态方法可以调用

如果一个接口中有多个默认方法, 并且方法中有重复的内容, 那么可以抽取出来, 封装到私有方法中, 供默认方法去调用; 从设计的角度讲, 私有的方法是对默认方法和静态方法的辅助

// 定义接口
public interface LiveAble{
    default void func(){
        // 在默认方法当中调用私有方法, 实现类不可调用私有方法
        func1();
        func2();
    }
    
    private void func1(){
        System.out.println("跑起来!!!");
    }
    
     private void func2(){
        System.out.println("跑起来!!!");
    }
}

接口中的常量和使用

接口当中也可以定义"成员变量", 但是必须使用public static final 三个关键字进行修饰,从效果上看,这就是[常量], 一旦使用final关键字进行修饰,说明不可变;

格式 : public static final 数据类型 常量名称 = 数据值;

注意事项:

  1. 接口当中的常量, 可以省略public static final ,注意 : 不写也照样是这样
  2. 接口当中的常量, 必须进行赋值, 不能不赋值
  3. 接口当中常量的名称 ,使用完全大写的字母, 用下划线进行分割(推荐命名规则)

接口的多实现

在继承体系中 , 一个类只能继承一个父类; 而对于接口而言, 一个类是可以实现多个接口的, 这叫做接口的多实现; 并且, 一个类能在继承一个父类的同时, 实现多个接口的基础

class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3{
    // 重写接口中抽象方法[必须]
    // 重写接口中默认方法[不重名名可选]
    // []: 可选操作
}

// 抽象方法

接口当中, 有多个抽象方法时 , 实现类必须重写所有抽象方法,; 如果抽象方法有重名的, 只需要重写一次

// 定义多个接口
interface A{
    public abstract void showA();
    public abstract void show();
}
interface B{
    public abstract void showB();
    public abstract void show();
}


// 定义实现类
public class C implements A,B{
    @Override
    public void showA{
        System.out.println("showA");
    }
    
    @Override
    public void showB{
        System.out.println("showB");
    }
    
    @Override
    public void show{
        System.out.println("show"); //在实现类当中,只重写一次接口当中重名的方法
    }
}

// 默认方法

接口中, 有多个默认方法时, 实现类都可继承使用; 如果默认方法有重名的, 必须重写一次

// 定义多个接口
interface A{
    public default void methodA();
    public default void method();
}
interface B{
    public default void methodB();
    public default void method();
}


// 定义实现类
public class C implements A,B{
    @Override
    public void method{
        System.out.println("method");
    }
}

// 静态方法

接口中, 存在同名的静态方法并不会冲突, 原因是只能通过各自的接口名访问静态方法; 不能通过实现类访问

优先级问题

当一个类, 既继承一个父类, 又实现若干个接口时, 父类中的成员方法与接口中的默认方法重名, 此时子类就近执行父类的成员方法

接口中, 无法定义成员变量, 但是可以定义成员常量, 其值不可改变, 默认使用 public static final 修饰

接口中, 没有构造方法, 不能创建对象; 也没有静态代码块

2 多态

多态是继封装, 继承之后, 面向对象的第三大特性; extends或者implements实现, 是多态性的前提

多态 : 是指同一行为, 具有多个不同表现形式; 代码的体现 --> 父类引用指向子类对象

多态的体现在多态的格式 : 父类名 对象名 = new 子类名 实例名.方法()

父类名 : 指子类对象继承的父类类型, 或者实现的父接口类型

Fu f = new Zi();
f.method();

当使用多态方式调用方法时, 首先检查父类中是否有该方法, 如果没有,则编译错误; 如果有, 执行的是子类重写后的方法

// 定义父类
public abstract  class Animal{
    public abstratc void eat();
}


// 定义子类
class Cat extends Animal{
    public void eat(){
        System.out.println("吃鱼");
    }
}
class Dog extends Animal{
    public void eat(){
        System.out.println("吃骨头");
    }
}


// 定义测试类
public class Test{
    public static void main(String args){
        // 多态形式, 创建多些
        Animal a1 = new Cat();
        a1.eat(); // 调用的是 Cat的eat()
        
        Animal a2 = new Dog();
        a2.eat();  // 调用的是 Dog的eat()
    }
}

使用多态的好处

无论右边new 的时候, 换成那个子类对象, 等号左边调用方法都不会变化

引用类型转换

// 向上转型

多态本身是子类类型向父类类型向上转换的过程, 这个过程是默认的当父类引用指向一个子类对象时, 便是向上转型, 它一定是安全的; 但是它无法调用子类原本的内容

使用格式 : 父类类型 变量名 = new 子类类型(); Animal a = new Cat();

// 向下转型

父类类型向子类类型向下转换的过程,是强制的; 一个已经向上转型的子类对象, 将父类引用转为子类引用, 可以使用强制类型转换的格式, 便是向下转型

使用格式 : 子类类型 变量名 = (子类类型) 父类变量名; Cat a = (Cat) a;

转型的异常 : cat 向上转型为 Animal, dog 向上转型为 Animal; cat再次向下转型的时候, 要防止不能转型成dog; 需要使用 instance of 关键字, 给引用变量做类型的校验 ; 对象名 instanceof 类名称

public class Test {
    public static void main(String[] args) {
        // 向上转型
        Animal a = new Cat();
        a.eat();// 调用的是 Cat 的 eat
        // 向下转型;判断父类引用a本来是不是dog
        if (a instanceof Cat){
            c = (Cat)a;
            c.catchMouse();// 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
            Dog d = (Dog)a;
            d.watchHouse();// 调用的是 Dog 的 watchHouse
        }
    }
}

3 final

final关键字 : 用于修饰不可改变的内容, 可以用于修饰类, 方法, 和变量

对于类, 方法来说 ; abstract和final关键字不能同时存在

类 : 被修饰的类, 不能被继承; 即不能有子类

方法 : 被修饰的方法, 不能被重写

变量 : 被修饰的变量 , 不能被重新赋值; 只能有唯一的一次赋值

​ // 对于基本类型来说 , 不可变说的是变量当中的数据不可变

​ // 对于引用类型来说 , 不可变说的是变量当中的地址值不可变

​ // 由于成员变量有默认值, 所以用了final之后必须手动赋值, 不会 再给默认值

// 修饰类
final class 类名{
	// 代码块
}


// 修饰方法
修饰符 final 返回值类型 方法名(参数列表){
    // 方法体
}


// 修饰变量
final 变量类型 变量名 = 值;

4 四种权限修饰符

在Java中, 提供了四种访问权限, 使用不同的访问权限修饰符修饰时, 被修饰的内容会有不同的访问权限

public : 公共的

protected : 受保护的

default(非关键字,而是默认不写) : 默认的

private : 私有的

public protected default private
同一类中
同一包中(子类与无关类)
不同包的子类
不同包的无关类

可见 , public具有最大权限, private则是最小权限; 编写代码时, 如果没有特殊的考虑, 建议这样使用权限

成员变量使用 public , 隐藏细节

构造方法使用public, 方便创建对象成员

方法使永public, 方便调用方法

5 内部类

身体和心脏的关系? 汽车和发动机的关系?

定义 : 内部类将一个类A 定义在类B里面, 里面的那个类A就成为内部类, B则称为外部类

成员内部 : 定义在类中, 方法外的类

访问特点 : 内部类可以直接访问外部类的成员, 包括私有成员; 外部类要访问内部类的成员, 必须要建立内部类的对象

创建内部类对象格式 : 外部类名.内部类名 对象名 = new 外部类名().new 内部类名

// 定义格式
public class 外部类名{
    class 内部类名{
        
    }
}



// 创建内部类对象格式
public class Person{
    private boolean live = true;
    
    public class Heart{
        public void jump(){
            // 直接访问外部类成员
            if(live){
                System.out.println("心脏在跳动");
            }else{
                System.out.println("心脏在跳动");
            }
        }
        public boolean isLive(){
            return live;
        }
        public void setLive(boolean live){
            this.live = live;
        }
    }
    // ==================================
    public static void main(String[] args){
        // 创建外部类对象
        Person p = new Person()
        // 创建内部类对象
        Heart heart = p.new Heart();
        
        //调用内部类方法
        heart.jump();
        // 外部类调用内部类方法
        p.setLive(false);
       
    }
}

如何使用内部类? 有两种方式:

  1. 间接方法 : 在外部类的方法当中, 使用内部类, 然后main()只是调用外部类的方法
  2. 直接方式 : 在外部类的main()中, 先创建内部类对象, 在调用内部类的方法
  3. 内部类访问外部类的成员变量(重名), 使用 Outer.this.成员变量名, 加上 Outer

局部内部类

定义在外部类的成员方法的内部, 不能使用任何修饰符 直接 class 类名 {}

局部内部类, 如果希望访问所在方法的局部变量, 那么这个局部变量必须是有效final的,这与生命周期有关,与内存的分布

匿名内部类

如果接口的实现类(或者是父类的子类), 只需要使用唯一的一次, 那么这种情况下就可以省略该类的定义, 而改为使用匿名内部类; 它的本质是一个 带具体实现的 父类或者父接口的 匿名的 子类对象

前提 : 必须继承一个父类 或者 实现一个父接口

定义格式 :

接口名称 对象名 = new 接口名称() {
    // 覆盖重写所有抽象方法
};

注意事项 :

  1. 匿名颞部类,在[创建对象]的时候, 只能使用唯一的一次; 如果希望多次创建对象, 而且类的内容一样的话 , 那么就必须使用单独定义的实现类了
  2. 匿名对象, 在调用方法的时候, 只能调用唯一的一次; 如果希望同一个对象, 调用多次方法, 那么必须给起个名字, 这是就不是匿名对象了
  3. 匿名内部类是省略了实现类/子类名称, 但是匿名对象是省略了对象名称; 匿名内部类和和匿名对象不是一回事

6 小结

类作为成员变量类型

自定义类的类型可以作为 其他类的成员变量 使用

接口作为成员变量类型

自定义类 , 接口类 , 实现类; 自定义的类对象调用接口的方法,把接口对象作为方法的参数调用

接口作为方法的参数或者返回值
对象的动态写法 : List list = new ArrayList<> (); 左边接口名称, 右边是实现类名称

7 Object类

java.lang.Object类是Java语言中的根类, 即所有类的父亲, 它中描述的所有方法子类都可以使用, 在对象实例化的时候, 最终找的父类就是Object; 如果一个类没有特别指定父类, 那么默认则继承自Object类

接下来主要学习其中的两个

  • public String toString() : 返回该对象的字符串表示; 内容为 对象类型+@+内存地址值
  • public boolean equals(Object obj) : 指示其他某个对象是否与此对象相等

重写toString()

如果不希望使用toString方法的默认行为, 则可以对它进行覆盖重写

public class Person{
    private String name;
    private int age;
    
    @Override
    public String toString() {
      return "Person name:" +name+',' + "age:" +age;
    }
    
}

重写equals()

默认的Object类 比较的是 " == ", 只要不是同一个对象, 则必然返回false; 因此意义不大

mport java.util.Objects;
public class Person {    
    private String name;
    private int age;
    @Override
    public boolean equals(Object o) {
        // 如果对象地址一样,则认为相同
        if (this == o)
            return true;
        // 如果参数为空,或者类型信息不一样,则认为不同
        if (o == null || getClass() != o.getClass())
            return false;
        // 转换为当前类型
        Person person = (Person) o;
        // 要求基本类型相等,并且将引用类型交给java.util.Objects类的equals静态方法取用结果
        return age == person.age && Objects.equals(name, person.name);
    }
}

Objects类

在JDK7添加了一个Objects类在java.utils.Objetcs, 它提供了一些方法来操作对象,它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。

比较两个对象的时候,Object的equals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题, 源码如下 :

public static boolean equals(Object a, Object b) {  
    return (a == b) || (a != null && a.equals(b));  
}

// Objects.equals(s1,s2)

8 日期时间类

Date类

java.util.Date 类 表示特定的瞬间, 精确到毫秒; 它的构造函数可以将毫秒值转成日期对象

  • public Date() 分配Date对象并初始化此对象, 以表示分配它的时间(精确到毫秒)
  • public Date(long date) 分配Date对象并初始化此对象, 以表示自从标准基准时间(即1970年1月1日00:00:00)以来的指定毫秒数

我们处于东八区, 基准时间为1970年1月1日08:00:00

简单来说使用无参构造, 可以自动设置当前系统时间的毫秒时刻; 指定long类型的构造参数, 可以自定义毫秒时刻

Date类的成员方法和构造方法

构造方法 : new Date时 传入一个long类型, 生成一个日期对象

成员方法 : getTime() 把日期对象转换成long类型的毫秒值

##  DateFormat类

java.text.DateFormat 是日期/时间格式化子类的抽象类, 我们通过这个类可以帮助我们完成日期和文本之间的转换, 也就是可以在Date对象与String对象之间进行来回转换

  • 格式化 : 按照指定的格式, 从Date对象转换为String对象
  • 解析 : 按照指定的格式, 从String对象转换为Date对象

构造方法

由于DateFormat为抽象类, 不能直接使用, 所以需要使用的子类 java.text.SimpleDateFormat… 这个类需要一个格式指定格式化或解析的规则

public SimpleDateFormat(String pattern) : 用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat, 参数pattern是一个字符串, 代表日期时间的自定义格式

格式规则 :

标识字母(区分大小写) 含义
y
M
d
H
m
s
// 创建SimpleDateFormat对象

import java.text.DateFormat
import java.text.SimpleDateFormat

public clas DemoSimpleDateFormat{
    public static void main(String[] args){
        DateFormat datetime = new DemoSimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    }
}

常用方法

  • public String format(Date date) : 将Date对象格式化为字符串
  • public Date parse(String source) : 将字符串解析为Date对象

// format()

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

/*  把Date对象转换为String */

public class DemoDateFormateMethod{
    public static void main(String[] args){
        Date date = new date();
        
        // 创建日期格式化对象, 在获取格式化对象时可以指定风格
        DateFormat df = new SimpleDateFormat("yyyy年MM月dd日")
        String str = df.format(date);
        System.out.println(srt); // 2018年1月23日
    }
}

// parse

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
 把String转换成Date对象
*/
public class Demo04DateFormatMethod {
    public static void main(String[] args) throws ParseException {
        DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");
        String str = "2018年12月11日";
        Date date = df.parse(str);
        System.out.println(date); // Tue Dec 11 00:00:00 CST 2018
    }
}

练习 : 计算一个人已经出生了多少天了!

package cn.itcast.day11;


import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/*
1 使用Scanner类, 获取输入的出生日期
2 使用DateFormat的parse, 将字符串的出生日期,解析为Date格式
3 把Date格式的出生日期转换为毫秒值
4 获取当前的日期, 转换为毫秒值
5 (当前时间的毫秒值 - 出生日期的毫秒值) / 1000/60/60/24
 */
public class Demo01CountTime {

	public static void main(String[] args) throws ParseException {

		System.out.println("请输入你的出生日期 : 格式为yyyy-MM-dd");
		// 获取出生日期的指定格式的字符串
		String bthStr = new Scanner(System.in).next();

		// 指定要转换成什么格式的日期
		DateFormat df = new SimpleDateFormat("yyyy-MM-dd");

		// 出成日期对象
		Date bthDate = df.parse(bthStr);
		// 今天的日期对象
		Date todayDate = new Date();

		// 毫秒值
		long bthSecond = bthDate.getTime();
		long todaySecond = todayDate.getTime();

		// 差值
		long secone = todaySecond - bthSecond;
		if (secone < 0){
			System.out.println("我还没出生呢!");
		}else{
			System.out.println("出生了"+secone/1000/60/60/24+"天");
		}
	}
}

Calendar类

java.util.Calendar 它是一个 日历 类, 在Date后出现, 替换了许多Date的方法, 该类将所有可能用到的时间信息封装为静态成员变量, 方便获取, 日历类就是方便获取各个时间属性的

获取方式

Calendar为抽象类 , 由于语言的敏感性 , Calendar在创建对象的时候并非 new 对象 , 而是通过静态方法创建, 返回子类对象,

public static Calendar getInstance() : 使用默认时区和语言环境获得一个日历

举例 : Calendar cal = Calendar.getInstance();

常用方法

public int get(int field) : 返回给定日历字段的值

public void set(int field, int value) : 将给定的日历字段设置为给定值

public abstract void add(int field, int amount) : 根据日历的规则, 为给定的日历字段添加或减去指定的时间量

public Date getTime() : 返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量)的Date对象

日历字段表 :

字段值 含义
YEAR
MONTH 月(从0开始, 可以+1使用)
DAY_OF_MONTH 月中的天 (几号)
HOUR 时(12小时制)
HOUR_OF_DAY 时(24小时制)
MINUTE
SECOND
DAY_OF_WEEK 周中的天(周日为1, 可以-1使用)

// get/set方法

import java.util.Calendar

public class CalendarUtil{
    public static void main(String[] args){
        // 创建Calendar对象
        Calendar cal = Calendar.getInstance();
        
        // 设置 year 2000
        cal.set(Calendar.YEAR,2000);
        
        // 获取年
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH)+1;
        int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
        
        System.out.println(year+"年"+month+"月"+dayOfMonth+"日");
        
        // 2000年5月10日
    }
}

// add方法

add() 可以对指定日历字段的值就行加减操作, 如果第二个参数为正数则加上偏移量, 如果为负数则减去偏移量

import java.util.Calendar;
public class Demo08CalendarMethod {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2018年1月17日
        // 使用add方法
        cal.add(Calendar.DAY_OF_MONTH, 2); // 加2天
        cal.add(Calendar.YEAR, -3); // 减3年
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2015年1月18日; 
    }
}

// getTime方法

Calendar中的getTime方法并不是获取毫秒时刻, 而是拿到对应的Date对象

import java.util.Calendar;
import java.util.Date;
public class Demo09CalendarMethod {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        Date date = cal.getTime();
        System.out.println(date); // Tue Jan 16 16:03:09 CST 2018
    }
}

小贴士 :

西方的周日为一周的开始

在Calendar类中, 月份从0 开始

9 System类

java.lang.System 类中提供了大量的静态方法, 可以获取与系统相关的信息或者系统级操作, 在System类的API文档有 :

public static long currentTimeMillis() : 返回以毫秒为单位的当前时间

public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)
将数组中指定得数据拷贝到另外一个数组中

currentTimeMillis方法

实际上, currentTimeMillis() 就是获取当前系统时间与历元之间的毫秒差值

import java.util.Date;
public class SystemDemo {
    public static void main(String[] args) {
           //获取当前时间毫秒值
        System.out.println(System.currentTimeMillis()); // 1516090531144
    }
}

arraycopy方法

数组的拷贝是系统级的, 性能很高 ; System.arraycopy方法具有五个参数, 含义分别为

参数序号 参数名称 参数类型 参数含义
1 src Object 源数组
2 srcPos int 源数组索引起始位置
3 dest Object 目标数组
4 destPos int 目标数组索引起始位置
5 length int 复制元素个数

练习 : src数组元素[1,2,3,4,5],dest数组元素[6,7,8,9,10]; 将src数组中前3个元素,复制到dest数组的前3个位置上复制元素前

import java.util.Arrays

public class Demo01SystemArrayCopy{
    
    public static void main(String[] args){
        int[] src = new int[]{1,2,3,4,5};
        int[] src = new int[]{1,2,3,4,5};
        System.arraycopy(src,0,dest,0,3)
        // src : [ 1,2,3,4,5]
        // dest : [1,2,3,9,10]
       
    }
}

10 StringBuilder类

字符串拼接问题

由于String类的内容不可变, 所以我们进行字符串拼接的时候, 总是会在内存中新建一个对象

public class StringDemo{
    public static void main(String[] args){
        String str = "Hello";
        str += "world";
        System.out.println(str)
    }
}

在APi中对String类有这样的描述 : 字符串是常量, 他们的值在创建后不能更改

以上的案例中, 先创建一个"Hello", 引用sre指向它, 虽有又创建一个"World", 改变str的引用指向它, 随后有拼接形成一个新的字符串对象, 继续改变str的引用指向

Stringbuilder概述

java.lang.StringBuilder 查阅API文档, StringBuilder 又称为可变字符序列, 它是一个类似于String的字符串缓冲区 , 通过某些方法调用可以改变该序列的长度和内容

StringBuilder是个字符串的缓冲区,它是一个容器, 容器可以装很多字符串, 并且能够对其中的字符串进行各种操作

她的内部拥有一个数组用来存放字符串内容, 进行字符串拼接时, 直接在数组中加入新的内容, StringBuilder会自动维护数组的扩容(默认16字符空间, 超过自动扩充)

构造方法

public StringBuilder() : 无参构造, 构造一个空的StringBuilder容器

public StringBuilder(String str) : 构造一个StringBuilder, 并将字符串添加进去

public class StringBuilderDemo {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder();
        System.out.println(sb1); // (空白)
        // 使用带参构造
        StringBuilder sb2 = new StringBuilder("itcast");
        System.out.println(sb2); // itcast
    }
}

常用方法

public StringBuilder append() : 添加任意类型数据的字符串形式, 并返回当前对象本身

public StringBuilder toString : 将当前StringBuilder对象转换为String对象

// append方法

append方法具有多种重载形式,可以接收任意类型的参数。任何数据作为参数都会将对应的字符串内容添加到StringBuilder中

public class Demo02StringBuilder {
    public static void main(String[] args) {
        //创建对象
        StringBuilder builder = new StringBuilder();
        //public StringBuilder append(任意类型)
        StringBuilder builder2 = builder.append("hello");
        //对比一下
        System.out.println("builder:"+builder);
        System.out.println("builder2:"+builder2);
        System.out.println(builder == builder2); //true
        // 可以添加 任何类型
        builder.append("hello");
        builder.append("world");
        builder.append(true);
        builder.append(100);
        // 在我们开发中,会遇到调用一个方法后,返回一个对象的情况。然后使用返回的对象继续调用方法。
        // 这种时候,我们就可以把代码现在一起,如append方法一样,代码如下
        //链式编程
        builder.append("hello").append("world").append(true).append(100);
        System.out.println("builder:"+builder);
    }
}

StringBuilder已经覆盖重写了Object当中的toString方法

// toString方法

通过toString方法,StringBuilder对象将会转换为不可变的String对象

public class Demo16StringBuilder {
    public static void main(String[] args) {
        // 链式创建
        StringBuilder sb = new StringBuilder("Hello").append("World").append("Java");
        // 调用方法
        String str = sb.toString();
        System.out.println(str); // HelloWorldJava
    }
}

11 包装类

Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下:

基本类型 对应的包装类(位于java.lang包中)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

装箱与拆箱

基本类型与对应的包装类之间, 来回转换的过程称为"装箱"与"拆箱"

装箱 : 从基本类型转换为对应的包装类对象

Integer i = new Integer(4); // 使用构造函数
Integer iii = Integer.valueOf(4); // 使用包装类中的valueOf


// 以上都是创建了一个int值为4的Integer类的对象

拆箱 : 从包装类对象转换为对应的基本类型

int num = i.intValue()

// 将一个int值为4的Integer类的对象转换为一个int类型的变量

自动装箱与自动拆箱**

由于我们经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成

Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。

基本类型与字符串之间的转换

基本类型 --> 字符串类型

  1. 使用 + 拼接

  2. 包装类的静态方法toString(参数), 不是Objetc类的toString() 重载

    static String toString(int i)返回一个表示指定整数的 String 对象

  3. String类的静态方法valueOf(参数)

字符串类型 --> 基本类型

除了Character之外, 其他的包装类都有parseXxxxx(“数值类型的字符串”)的静态方法

  • public static byte parseByte(String s) : 将字符串参数转换为对应的byte基本类型
  • public static byte parseByte(String s) : 将字符串参数转换为对应的byte基本类型
  • public static byte parseShort(String s) : 将字符串参数转换为对应的byte基本类型
  • public static byte parseInt(String s) : 将字符串参数转换为对应的byte基本类型
  • public static byte parseLong(String s) : 将字符串参数转换为对应的byte基本类型
  • public static byte parseFloat(String s) : 将字符串参数转换为对应的byte基本类型
  • public static byte parseDouble(String s) : 将字符串参数转换为对应的byte基本类型
  • public static byte parseBoolean(String s) : 将字符串参数转换为对应的byte基本类型
public class Demo01WrapperParse{
    public static void main (String[] args){
        int num = Integer.parseInt("100")
    }
}

12 Collection类

集合概述

  • 集合是java中的一种容器, 可以用来存储多个数据

集合和数组既然都是容器, 他们有啥区别呢

  • 数组的长度是固定的, 集合的长度是可变的
  • 数组中存储的是同一类型的元素, 可以存储基本类型的数据值; 集合存储的都是对象, 而且对象的类型可以不一致; 在开发中一般当对象多的时候, 使用集合进行存储

集合框架

集合按照存储结构分为两大类, 分别是单列集合java.util.Collection和双列集合java.util.Map

  • Collection : 单列集合类的根接口, 用于存储一系列复合某种规则的元素, 它有两个重要的子接口, 分别是java.util.Listjava.util.Set, 其中, List的特点是元素有序, 元素可重复; Set的特点是元素无序, 不可重复; List接口的主要实现类有java.util.ArrayListjava.util.LinkedList, Set接口的主要实现类有java.util.HashSetjava.util.TreeSet

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QLcOjpfi-1589289361015)(assets/1589122270152.png)]

所有单列集合的共性方法

public boolean add(E e) : 把给定的对象添加到当前集合中

public void clear() : 清空集合中所有的元素

public boolean contains(E e) : 判断当中集合中是否包含给定的对象

public boolean remove(E e) : 把给定的对象在当前集合中删除

public boolean isEmpty() : 判断当中集合中是否为空

public int size() : 返回集合中元素的个数

public Object toArray() : 把集合中的元素 ,存储到数组中

13 Iterator迭代器

在程序开发中, 经常需要遍历集合中的所有元素; JDK专门提供了一个接口java.util.Iterator; Iterator接口也是Java集合中的一员, 它与Collection,Map接口有所不同, Collecion接口与Map接口主要用于存储元素, 而Iterator主要用于迭代访问(遍历) Collection中的元素, 因此, Iterator对象也被称为迭代器

想要遍历Collection集合, 那么就要获取该集合迭代器完成迭代操作

  • public Iterator iterator() : 获取集合对应的迭代器, 用来遍历集合中的元素的

迭代的概念

  • 它是Colletion集合元素的通用获取方式; 在取元素之前要判断集合中有没有元素, 如果有, 就把这个元素取出来, 继续在判断, 如果还有就再取出来; 一直把集合中的所有元素全部取出; 这种取出方式叫做迭代

Iterator接口的常用方法

  • public E next() : 返回迭代的下一个元素
  • public boolean hasNext() : 如果有元素可以迭代, 则返回true

// 代码实现

public class Demo01Iterator{
    public static void main(String[] args){
        // 创建一个集合对象
        Collection<String> coll = new  ArrayList<>();
        // 往集合中添加元素
        coll.add("姚明");
        coll.add("科比");
        coll.add("麦迪");
        
        // 多态 左接口     右实现类对象
        Iterator<String> it = coll.iterator();
        System.out.println(it.next());
        
        // 一次取出迭代器的所有元素
        while(it.hasNext()){
            System.out.println(it.next());
        }
    } 
}

// 迭代器实现原理

Iterator it = coll.iterator() 获取迭代器的实现类对象, 并且会把指针(索引) 指向集合的-1索引; next()不单单取出下一个元素, 同时也会把指针向后移动一位, 指向下一位元素

增强for

增强 for循环也称 for each 循环, 是专门用来遍历数组和集合的, 它的内部实现原理其实是个Iterator迭代器 , 所有在遍历过程中, 不能对集合的元素进行增删改查

格式 : for (集合/数组的数据类型 变量名 : 集合名或者数组名) { }

// 遍历数组

public class NBForDemo{
    public static void main(String[] args){
        int[] arr = {3,5,6,4,87};
        // 使用增强for遍历数组
        for(int a:arr){ // a是个变量, 代表数组中的每个元素
            System.out.println(a);
        }
    }
}

14 泛型

泛型 : 不确定的数据类型, 未知的数据类型

好处 :

  1. 避免了类型转换的麻烦, 存储的是什么类型, 取出的就是什么类型
  2. 把运行期一次(代码运行之后会抛出的异常), 提升到了编译期(写代码运行之后的时候)

泛型的定义与使用

我们在集合中会大量使用到泛型, 它能够用来灵活地将数据类型应用到不同的类, 方法, 接口当中; 将数据类型作为参数传递

定义格式 : 修饰符 class 类名<代表泛型的变量>{ }

public class ArrayList<E>{
    public boolean add(E e){}
    
    public E get(int index){}
}

使用 : 我们在定义类的时候, 不要固定死 参数类型, 还用 E 来指明它是一个泛型, 这样在使用它的时候, 就可以传递任意的参数类型作为参数, 十分方便

定义含有泛型的方法

格式 
	修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)) { }
	
	含有泛型的方法, 在调用方法的时候确定泛型的数据类型
	方法传递什么类型的参数, 泛型就是什么数据类型
	
public class DenericMethod{
    
    public <E> void method01(E e){
        System.out.println(e);
    }
}

定义含有泛型的接口

接口的参数类型为泛型, 那么实现类的参数就也是泛型; 在调用实现类的方法时, 传递的参数, 就是泛型的 数据类型

泛型通配符

当使用泛型类或者接口时, 传递的数据中, 泛型类型不确定, 可以通过通配符表示 ; 但是一旦使用泛型的通配符, 就只能使用Object类中的共性方法, 集合中元素自身无法使用

使用 : 不知道使用什么数据类型来接受的时候, 此时可以使用?, ? 表示未知通配符; 此时不能接受数据, 只能往集合中存储数据 ; 这时不能创建对象使用, 只能作为方法的参数使用

import java.util.ArrayList;
import java.util.Iterator;

public class Demo01Generic {

	public static void main(String[] args) {
		ArrayList<String> list01 = new ArrayList<>();
		list01.add("a");
		list01.add("b");

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

		printArray(list01);
		printArray(list02);
	}

	public static void printArray(ArrayList<?> list){
		Iterator it = list.iterator();
		while (it.hasNext()){
			System.out.println(it.next());
		}

	}
}

类名<代表泛型的变量>{ }

public class ArrayList<E>{
    public boolean add(E e){}
    
    public E get(int index){}
}

使用 : 我们在定义类的时候, 不要固定死 参数类型, 还用 E 来指明它是一个泛型, 这样在使用它的时候, 就可以传递任意的参数类型作为参数, 十分方便

定义含有泛型的方法

格式 
	修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)) { }
	
	含有泛型的方法, 在调用方法的时候确定泛型的数据类型
	方法传递什么类型的参数, 泛型就是什么数据类型
	
public class DenericMethod{
    
    public <E> void method01(E e){
        System.out.println(e);
    }
}

定义含有泛型的接口

接口的参数类型为泛型, 那么实现类的参数就也是泛型; 在调用实现类的方法时, 传递的参数, 就是泛型的 数据类型

泛型通配符

当使用泛型类或者接口时, 传递的数据中, 泛型类型不确定, 可以通过通配符表示 ; 但是一旦使用泛型的通配符, 就只能使用Object类中的共性方法, 集合中元素自身无法使用

使用 : 不知道使用什么数据类型来接受的时候, 此时可以使用?, ? 表示未知通配符; 此时不能接受数据, 只能往集合中存储数据 ; 这时不能创建对象使用, 只能作为方法的参数使用

import java.util.ArrayList;
import java.util.Iterator;

public class Demo01Generic {

	public static void main(String[] args) {
		ArrayList<String> list01 = new ArrayList<>();
		list01.add("a");
		list01.add("b");

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

		printArray(list01);
		printArray(list02);
	}

	public static void printArray(ArrayList<?> list){
		Iterator it = list.iterator();
		while (it.hasNext()){
			System.out.println(it.next());
		}

	}
}

受限泛型 的上限于 下限 : 规定传递的泛型只能是某个的本身或者是他的子类与 父类

你可能感兴趣的:(黑马学习笔记)