chapter08 面向对象编程高级 知识点总结Note


static修饰符

变量的分类
方式1:按照数据类型:基本数据类型、引用数据类型

方式2:按照类中声明的位置:
成员变量:按照是否使用static修饰进行分类:
使用static修饰的成员变量:静态变量、类变量
不使用static修饰的成员变量:非静态变量、实例变量

局部变量:方法内、方法形参、构造器内、构造器形参、代码块内等。

如果想让一个成员变量被类的所有实例所共享,就用static修饰即可,称为类变量(或类属性)!

对比静态变量与实例变量:
① 个数
>静态变量:在内存空间中只有一份,被类的多个对象所共享。
>实例变量:类的每一个实例(或对象)都保存着一份实例变量。
② 内存位置
>静态变量:jdk6及之前:存放在方法区。 jdk7及之后:存放在堆空间
>实例变量:存放在堆空间的对象实体中。
③ 加载时机
>静态变量:随着类的加载而加载,由于类只会加载一次,所以静态变量也只有一份。
>实例变量:随着对象的创建而加载。每个对象拥有一份实例变量。
④ 调用者
>静态变量:可以被类直接调用,也可以使用对象调用。
>实例变量:只能使用对象进行调用。
⑤ 判断是否可以调用 —> 从生命周期的角度解释

​ ⑥ 消亡时机
​ >静态变量:随着类的卸载而消亡
​ >实例变量:随着对象的消亡而消亡

在类中声明的实例方法,在类的外面必须要先创建对象,才能调用。但是有些方法的调用者和当前类的对象无关,这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。

这里的类变量、类方法,只需要使用static修饰即可。所以也称为静态变量、静态方法

  • 使用范围:

    • 在Java类中,可用static修饰属性、方法、代码块、内部类
  • 被修饰后的成员具备以下特点:

    • 随着类的加载而加载
    • 优先于对象存在
    • 修饰的成员,被所有对象所共享
    • 访问权限允许时,可不创建对象,直接被类调用

静态变量

使用static修饰的成员变量就是静态变量(或类变量、类属性)

静态变量的特点

  • 静态变量的默认值规则和实例变量一样。

  • 静态变量值是所有对象共享。

  • 静态变量在本类中,可以在任意方法、代码块、构造器中直接使用。

  • 如果权限修饰符允许,在其他类中可以通过“类名.静态变量”直接访问,也可以通过“对象.静态变量”的方式访问(但是更推荐使用类名.静态变量的方式)。

  • 静态变量的get/set方法也静态的,当局部变量与静态变量重名时,使用“类名.静态变量”进行区分。

静态方法

用static修饰的成员方法就是静态方法。

静态方法的特点

  • 静态方法在本类的任意方法、代码块、构造器中都可以直接被调用。

  • 只要权限修饰符允许,静态方法在其他类中可以通过“类名.静态方法“的方式调用。也可以通过”对象.静态方法“的方式调用(但是更推荐使用类名.静态方法的方式)。

  • 在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。

  • 静态方法可以被子类继承,但不能被子类重写。

  • 静态方法的调用都只看编译时类型。

  • 因为不需要实例就可以访问static方法,因此static方法内部不能有this,也不能有super。如果有重名问题,使用“类名.”进行区别。

  • 随着类的加载而加载

    可以通过“类.静态方法”的方式,直接调用静态方法
    静态方法内可以调用静态的属性或静态的方法。(属性和方法的前缀使用的是当前类,可以省略)
    不可以调用非静态的结构。(比如:属性、方法)

开发中,什么时候需要将属性声明为静态的?
> 判断当前类的多个实例是否能共享此成员变量,且此成员变量的值是相同的。
> 开发中,常将一些常量声明是静态的。比如:Math类中的PI

什么时候需要将方法声明为静态的?
> 方法内操作的变量如果都是静态变量(而非实例变量)的话,则此方法建议声明为静态方法
> 开发中,常常将工具类中的方法,声明为静态方法。比如:Arrays类、Math类

public class CircleTest {
    public static void main(String[] args) {
        Circle circle1 = new Circle();
        Circle circle2 = new Circle();
        System.out.println(circle1);
        System.out.println(circle2);
        Circle circle3 = new Circle(2.0);
        System.out.println(circle3);
        System.out.println(Circle.total);
    }
}
class Circle{
    private double radius;
    private int id;
    static int total;
    private static int init = 1001; // 定义同一个static


    /**
     * 创建Circle对象时实现id 自增
     */
    public Circle(){
        this.id = init;
        init++;
        total++;
    }

    @Override
    public String toString() {
        return "Circle{" +
                "radius=" + radius +
                ", id=" + id +
                '}';
    }
    public Circle(double radius) {
        this(); // 调用无参构造代码
        this.radius = radius;
    }
}

单例设计模式

设计模式是在大量的实践中总结理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。“套路”

经典的设计模式共有23种

创建型模式(Creational Patterns)

1 单例模式(Singleton)

确保一个类只有一个实例,并提供一个全局访问点。

2 工厂方法模式(Factory Method)

定义一个创建对象的接口,让子类决定实例化哪一个类。

3 抽象工厂模式(Abstract Factory)

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。

4 建造者模式(Builder)

使用多个简单的对象一步步构建成一个复杂的对象。

5 原型模式(Prototype)

通过复制现有的实例来创建新的实例,而不是通过构造函数创建。

结构型模式(Structural Patterns)

6 适配器模式(Adapter)

将一个类的接口转换成客户端所期望的另一种接口。

7 装饰器模式(Decorator)

动态地给对象添加额外的职责,提供了比继承更灵活的替代方案。

8 桥接模式(Bridge)

将抽象部分与实现部分分离,使它们可以独立地变化。

9 组合模式(Composite)

将对象组合成树形结构以表示“部分-整体”的层次结构。

10 外观模式(Facade)

提供一个统一的接口,用来访问子系统中的一群接口。

11 享元模式(Flyweight)

通过共享大量细粒度的对象来支持大量的细粒度对象的高效复用。

12 代理模式(Proxy)

为其他对象提供一个代理,以控制对这个对象的访问。

行为型模式(Behavioral Patterns)

13 模板方法模式(Template Method)

定义一个操作中的算法骨架,将一些步骤延迟到子类中。

14 策略模式(Strategy)

定义一系列算法,把它们一个个封装起来,并使它们可以互换。

15 观察者模式(Observer)

定义对象间的一种一对多依赖关系,以便当一个对象改变状态时,其依赖者都得到通知并被自动更新。

16 迭代器模式(Iterator)

提供一种方法顺序访问集合对象中的元素,而又不暴露集合的内部表示。

17 中介者模式(Mediator)

用一个中介对象来封装一系列的对象交互。

18 备忘录模式(Memento)

在不暴露对象实现细节的情况下,捕获对象的内部状态,并在之后将对象恢复到原先的状态。

19 命令模式(Command)

将请求封装为一个对象,从而使你可以使用不同的请求、队列请求和记录请求日志,以及支持可撤销操作。

20 状态模式(State)

允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

21 责任链模式(Chain of Responsibility)

为请求创建一个接收者的链,请求沿着这条链传递,直到有一个对象处理它。

22 访问者模式(Visitor)

表示一个作用于某对象结构中的各元素的操作,它可以在不改变各元素类的前提下定义作用于这些元素的新操作。

23 解释器模式(Interpreter)

给定一个语言,定义它的文法表示,并定义一个解释器,解释该语言中的句子。

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

单例模式实现思路

如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的

单例模式的两种实现方式

饿汉式 立即创建实例

class Singleton {
    // 1.私有化构造器
    private Singleton() {
    }

    // 2.内部提供一个当前类的实例
    // 4.此实例也必须静态化
    private static Singleton single = new Singleton();

    // 3.提供公共的静态的方法,返回当前类的对象
    public static Singleton getInstance() {
        return single;
    }
}

懒汉式

class Singleton {
    // 1.私有化构造器
    private Singleton() {
    }
    // 2.内部提供一个当前类的实例
    // 4.此实例也必须静态化
    private static Singleton single;
    // 3.提供公共的静态的方法,返回当前类的对象
    public static Singleton getInstance() {
        if(single == null) {
            single = new Singleton();
        }
        return single;
    }
}

饿汉式:

  • 特点:立即加载,即在使用类的时候已经将对象创建完毕。
  • 优点:实现起来简单;没有多线程安全问题。
  • 缺点:当类被加载的时候,会初始化static的实例,静态变量被创建并分配内存空间,从这以后,这个static的实例便一直占着这块内存,直到类被卸载时,静态变量被摧毁,并释放所占有的内存。因此在某些特定条件下会耗费内存,内存中占用时间较长。。

懒汉式:

  • 特点:延迟加载,即在调用静态方法时实例才被创建。
  • 优点:实现起来比较简单;当类被加载的时候,static的实例未被创建并分配内存空间,当静态方法第一次被调用时,初始化实例变量,并分配内存,因此在某些特定条件下会节约内存
  • 缺点:在多线程环境中,这种实现方法是完全错误的,线程不安全,根本不能保证单例的唯一性。
    • 说明:在多线程章节,会将懒汉式改造成线程安全的模式。

单例模式的优点及应用场景

由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决

  • Windows的Task Manager (任务管理器)就是很典型的单例模式

  • Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

  • Application 也是单例的典型应用

  • 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只

    能有一个实例去操作,否则内容不好追加。

  • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源


main()方法

由于JVM需要调用类的main()方法,所以该方法的访问权限必须是public,又因为JVM在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。

又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到


类的成员 代码块

类的成员 属性 方法 构造器 代码块 内部类

作用 初始化 Java类或对象的信息

代码块 只能使用static 或缺省修饰 静态代码块 非静态代码块

静态代码块的特点

  1. 可以有输出语句。
  2. 可以对类的属性、类的声明进行初始化操作。
  3. 不可以对非静态的属性初始化。静态代码块 只能调用静态结构(属性方法)即:不可以调用非静态的属性和方法。
  4. 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
  5. 静态代码块的执行要先于非静态代码块。
  6. 静态代码块随着类的加载而加载,且只执行一次。

非静态代码块的执行特点

  1. 可以有输出语句。

  2. 可以对类的属性、类的声明进行初始化操作。

  3. 除了调用非静态的结构外,还可以调用静态的变量或方法。

  4. 若有多个非静态的代码块,那么按照从上到下的顺序依次执行。

  5. 每次创建对象的时候,都会执行一次。且先于构造器执行。随着对象创建而执行

非静态代码块的意义

​ 如果多个重载的构造器有公共代码,并且这些代码都是先于构造器其他代码执行的,那么可以将这部分代码抽取到非静态代码块中,减少冗余代码。相同代码 放到代码块中执行


实例变量赋值位置顺序

  1. 可以给类的非静态的属性(即实例变量)赋值的位置有:

    ① 默认初始化

    ② 显式初始化

    ③ 构造器中初始化

    ④ 有了对象以后,通过"对象.属性"或"对象.方法"的方法进行赋值

    ⑤ 代码块中初始化

  2. 执行的先后顺序:
    ① - ②/⑤ - ③ - ④

静态 代码块 构造器 对象

显示赋值:比较适合于每个对象的属性值相同的场景
构造器中赋值:比较适合于每个对象的属性值不相同的场景

class Root{
    static{
        System.out.println("Root的静态初始化块");
    }
    {
        System.out.println("Root的普通初始化块");
    }
    public Root(){
        super();
        System.out.println("Root的无参数的构造器");
    }
}
class Mid extends Root{
    static{
        System.out.println("Mid的静态初始化块");
    }
    {
        System.out.println("Mid的普通初始化块");
    }
    public Mid(){
        System.out.println("Mid的无参数的构造器");
    }
    public Mid(String msg){
        //通过this调用同一类中重载的构造器
        this();
        System.out.println("Mid的带参数构造器,其参数值:"
                + msg);
    }
}
class Leaf extends Mid{
    static{
        System.out.println("Leaf的静态初始化块");
    }
    {
        System.out.println("Leaf的普通初始化块");
    }
    public Leaf(){
        //通过super调用父类中有一个字符串参数的构造器
        super("尚硅谷");
        System.out.println("Leaf的构造器");
    }
}
//技巧:由父及子,静态先行。
public class LeafTest{
    public static void main(String[] args){
        new Leaf();
    }
}

final关键字

final可以用来修饰的结构:类、方法、变量

final修饰类:表示此类不能被继承。 比如:String、StringBuffer、StringBuilder类

final修饰方法:表示此方法不能被重写 比如:Object类中的getClass()

final修饰变量:既可以修饰成员变量,也可以修饰局部变量。 此时的"变量"其实就变成了"常量",意味着一旦赋值,就不可更改。

final修饰成员变量: 有哪些位置可以给成员变量赋值?

显式赋值
代码块中赋值
构造器中赋值

final修饰局部变量:一旦赋值就不能修改

方法内声明的局部变量:在调用局部变量前,一定需要赋值。而且一旦赋值,就不可更改
方法的形参:在调用此方法时,给形参进行赋值。而且一旦赋值,就不可更改

final与static搭配:修饰成员变量时,此成员变量称为:全局常量。
比如:Math的PI


abstract关键字使用

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。

​ 父类在声明这些方法时,就只有方法签名,没有方法体,我们把没有方法体的方法称为抽象方法。Java语法规定,包含抽象方法的类必须是抽象类

  • 抽象类:被abstract修饰的类。
  • 抽象方法:被abstract修饰没有方法体的方法。

抽象类

[权限修饰符] abstract class 类名{
}
[权限修饰符] abstract class 类名 extends 父类{
}

抽象方法

[其他修饰符] abstract 返回值类型 方法名([形参列表]);

//抽象父类 animal  定义抽象方法 eat
public abstract class Animal {
    public abstract void eat();
}
//子类cat继承animal 重写实现eat方法
public class Cat extends Animal {
    public void eat (){
      	System.out.println("小猫吃鱼和猫粮"); 
    }
}
public class CatTest {
 	 public static void main(String[] args) {
        // 创建子类对象
        Cat c = new Cat(); 
       
        // 调用eat方法
        c.eat();
  	}
}

1 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。

2 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。

3 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

4 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

Java允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。

  • 不能用abstract修饰变量、代码块、构造器;

  • 不能用abstract修饰私有方法、静态方法、final的方法、final的类。

abstract修饰类:

此类称为抽象类
抽象类不能实例化。
抽象类中是包含构造器的,因为子类对象实例化时,需要直接或间接的调用到父类的构造器。
抽象类中可以没有抽象方法。反之,抽象方法所在的类,一定是抽象类。

abstract修饰方法:

此方法即为抽象方法
抽象方法只有方法的声明,没有方法体。
抽象方法其功能是确定的(通过方法的声明即可确定),只是不知道如何具体实现(体现为没有方法体)
子类必须重写父类中的所有的抽象方法之后,方可实例化。否则,此子类仍然是一个抽象类。

public class AbstractTest {
    public static void main(String[] args) {
        Student student = new Student("张三",19,"明德中学");
        student.eat();
        student.sleep();
        System.out.println(student);
    }
}
abstract class Person{
    String name;
    int age;

    public Person(){}

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public abstract void eat(); //抽象方法

    public abstract void sleep(); //抽象方法
}

class Student extends Person{
    String school;

    public Student(){

    }
    public Student(String name, int age, String school) {
        super(name, age);
        this.school = school;
    }

    public void eat(){
        System.out.println("学生多吃有营养的食物");
    }

    public void sleep(){
        System.out.println("学生要保证充足的睡眠");
    }

    @Override
    public String toString() {
        return "Student{" +
                "school='" + school + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

抽象应用模板方法设计

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

解决的问题

  • 当功能内部一部分实现是确定的,另一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。

  • 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。

模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:

  • 数据库访问的封装

  • Junit单元测试

  • JavaWeb的Servlet中关于doGet/doPost方法调用

  • Hibernate中模板程序

  • Spring中JDBCTemlate、HibernateTemplate等

public class TemplateTest {
    public static void main(String[] args) {
        SubTemplate subTemplate = new SubTemplate();
        subTemplate.spendTime();
    }
}
abstract class Template {
    public final void spendTime() {
        long start = System.currentTimeMillis();
        code();
        long end = System.currentTimeMillis();
        System.out.println("消耗时间" + (end - start) + "ms");
    }

    public abstract void code();
}

class SubTemplate extends Template {
    @Override
    public void code() {
        int total = 0;
        for (int i = 0; i < 10000; i++) {
            if (i % 2 == 0) {
                total++;
            }
        }
        System.out.println("10000以内偶数个数为" + total);
    }
}
public class AbstractPersonTest {
    /**
     * 1、声明抽象父类Person,包含抽象方法public abstract void eat();
     * 2、声明子类中国人Chinese,重写抽象方法,打印用筷子吃饭
     * 3、声明子类美国人American,重写抽象方法,打印用刀叉吃饭
     * 4、声明测试类PersonTest,创建Person数组,存储各国人对象,并遍历数组,调用eat()方法
     */
    public static void main(String[] args) {
        AbsPerson[] absPeople = new AbsPerson[2];
        absPeople[0] = new Chinese();
        absPeople[1] = new American();
        for (int i = 0; i < absPeople.length; i++) {
            absPeople[i].eat();
        }
    }
}
abstract class AbsPerson {
    public abstract void eat();
}

class Chinese extends AbsPerson {
    @Override
    public void eat() {
        System.out.println("用筷子吃饭");
    }
}

class American extends AbsPerson {
    @Override
    public void eat() {
        System.out.println("用刀叉吃饭");
    }
}

一般提供无参和全参构造器


接口语法应用(多态 匿名实现类)

引用数据类型:数组,类,枚举,接口,注解。

接口的理解:接口的本质是契约、标准、规范,就像我们的法律一样。制定好后大家都要遵守。

定义接口的关键字:interface

[修饰符] interface 接口名{
    //接口的成员列表:
    // 公共的静态常量
    // 公共的抽象方法
    
    // 公共的默认方法(JDK1.8以上)
    // 公共的静态方法(JDK1.8以上)
    // 私有方法(JDK1.9以上)
}

接口内部结构的说明:

可以声明:
属性:必须使用public static final修饰
方法:jdk8之前:声明抽象方法,修饰为public abstract
jdk8:声明静态方法、默认方法
jdk9:声明私有方法
不可以声明:构造器、代码块等
接口与类的关系 :实现关系

在JDK8.0 之前,接口中只允许出现:

(1)公共的静态的常量:其中public static final可以省略

(2)公共的抽象的方法:其中public abstract可以省略

在JDK8.0 时,接口中允许声明默认方法静态方法

(3)公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略

(4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略

在JDK9.0 时,接口又增加了:

(5)私有方法

除此之外,接口中没有构造器,没有初始化块,因为接口中没有成员变量需要动态初始化。

  1. 如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法
  2. 默认方法可以选择保留,也可以重写。
  3. 接口中的静态方法不能被继承也不能被重写

格式:class A extends SuperA implements B,C{}

A相较于SuperA来讲,叫做子类 A相较于B,C来讲,叫做实现类

类可以实现多个接口。
类针对于接口的多实现,一定程度上就弥补了类的单继承的局限性。
类必须将实现的接口中的所有的抽象方法都重写(或实现),方可实例化。否则,此实现类必须声明为抽象类。
接口与接口的关系:继承关系,且可以多继承
接口的多态性: 接口名 变量名 = new 实现类对象;

面试题:区分抽象类和接口

共性:都可以声明抽象方法
都不能实例化
不同:① 抽象类一定有构造器。接口没有构造器
② 类与类之间继承关系,类与接口之间是实现关系,接口与接口之间是多继承关系

1 接口的多继承(extends)

一个接口能继承另一个或者多个接口,接口的继承也使用 extends 关键字,子接口继承父接口的方法。

2 接口的多实现(implements)

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

3 接口与实现类对象构成多态引用

实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。

4 使用接口的静态成员

接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量。

5 使用接口的非静态方法

  • 对于接口的静态方法,直接使用“接口名.”进行调用即可
    • 也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
  • 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
    • 接口不能直接创建对象,只能创建实现类的对象
public class UsbTest {
    public static void main(String[] args) {
        //1.创建接口实现类的对象
        Computer computer = new Computer();
        Printer printer = new Printer();
        computer.transferData(printer);
        //2.创建接口实现类的匿名对象
        computer.transferData(new Camera());
        //3.创建接口匿名实现类的对象
        USB usb1 = new USB() {
            @Override
            public void start() {
                System.out.println("U盘启动");
            }
            @Override
            public void stop() {
                System.out.println("U盘停止");
            }
        };
        computer.transferData(usb1);
        //4. 创建接口匿名实现类的匿名对象
        computer.transferData(new USB() {
            @Override
            public void start() {
                System.out.println("扫描仪开始工作");
            }
            @Override
            public void stop() {
                System.out.println("扫描仪结束工作");
            }
        });
    }
}

interface USB{
    public abstract void start();// 可省略 public abstract
    void stop();
}

class Printer implements USB{
    @Override
    public void start() {
        System.out.println("打印机开始工作");
    }

    @Override
    public void stop() {
        System.out.println("打印机结束工作");
    }
}
class Camera implements USB{
    @Override
    public void start() {
        System.out.println("照相机开始工作");
    }

    @Override
    public void stop() {
        System.out.println("照相机结束工作");
    }
}
class Computer {
    public void transferData(USB usb){
        System.out.println("设备链接成功");
        usb.start();
        System.out.println("数据传输中");
        usb.stop();
        System.out.println();
    }
}

jdk8 jdk9接口新特性

默认方法冲突问题

(1)类优先原则

当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名,子类就近选择执行父类的成员方法。代码如下:

(2)接口冲突(左右为难)

当一个类同时实现了多个父接口,而多个父接口中包含方法签名相同的默认方法时 选择保留其中一个,通过“接口名.super.方法名"的方法选择保留哪个接口的默认方法。

子接口重写默认方法时,default关键字可以保留。

子类重写默认方法时,default关键字不可以保留。

常量冲突问题

  • 当子类继承父类又实现父接口,而父类中存在与父接口常量同名的成员变量,并且该成员变量名在子类中仍然可见。
  • 当子类同时实现多个接口,而多个接口存在相同同名常量。

此时在子类中想要引用父类或父接口的同名的常量或成员变量时,就会有冲突问题。

  • 接口本身不能创建对象,只能创建接口的实现类对象,接口类型的变量可以与实现类对象构成多态引用。
  • 声明接口用interface,接口的成员声明有限制:
    • (1)公共的静态常量
    • (2)公共的抽象方法
    • (3)公共的默认方法(JDK8.0 及以上)
    • (4)公共的静态方法(JDK8.0 及以上)
    • (5)私有方法(JDK9.0 及以上)
  • 类可以实现接口,关键字是implements,而且支持多实现。如果实现类不是抽象类,就必须实现接口中所有的抽象方法。如果实现类既要继承父类又要实现父接口,那么继承(extends)在前,实现(implements)在后。
  • 接口可以继承接口,关键字是extends,而且支持多继承。
  • 接口的默认方法可以选择重写或不重写。如果有冲突问题,另行处理。子类重写父接口的默认方法,要去掉default,子接口重写父接口的默认方法,不要去掉default。
  • 接口的静态方法不能被继承,也不能被重写。接口的静态方法只能通过“接口名.静态方法名”进行调用。

面试题

1、为什么接口中只能声明公共的静态的常量?

因为接口是标准规范,那么在规范中需要声明一些底线边界值,当实现者在实现这些规范时,不能去随意修改和触碰这些底线,否则就有“危险”。

例如:USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA

​ USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA

例如:尚硅谷学生行为规范中规定学员,早上8:25之前进班,晚上21:30之后离开等等。

2、为什么JDK8.0 之后允许接口定义静态方法和默认方法呢?因为它违反了接口作为一个抽象标准定义的概念。

静态方法:因为之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这样成对的接口和类,后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的,那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便。

默认方法:(1)我们要在已有的老版接口中提供新方法时,如果添加抽象方法,就会涉及到原来使用这些接口的类就会有问题,那么为了保持与旧版本代码的兼容性,只能允许在接口中定义默认方法实现。比如:Java8中对Collection、List、Comparator等接口提供了丰富的默认方法。(2)当我们接口的某个抽象方法,在很多实现类中的实现代码是一样的,此时将这个抽象方法设计为默认方法更为合适,那么实现类就可以选择重写,也可以选择不重写。

3、为什么JDK1.9要允许接口定义私有方法呢?因为我们说接口是规范,规范是需要公开让大家遵守的。

私有方法:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法。

public class SubClassTest {
    public static void main(String[] args) {
        //知识点1:接口中声明的静态方法只能被接口来调用,不能使用其实现类进行调用。
        CompareA.method1();
        //SubClass.method1();
        //知识点2:接口中声明的默认方法可以被实现类继承,实现类在没有重写此方法的情况下,默认调用接口中声明的
        //默认方法。如果实现类重写了此方法,则调用的是自己重写的方法。
        SubClass s1 = new SubClass();
        s1.method2();
        //知识点3:类实现了两个接口,而两个接口中定义了同名同参数的默认方法。则实现类在没有重写此两个接口
        //默认方法的情况下,会报错。 ---->接口冲突
        //要求:此时实现类必须要重写接口中定义的同名同参数的方法。
        s1.method(); //重写method3
        //知识点4:子类(或实现类)继承了父类并实现了接口。父类和接口中声明了同名同参数的方法。(其中,接口中的方法
        //是默认方法)。默认情况下,子类(或实现类)在没有重写此方法的情况下,调用的是父类中的方法。--->类优先原则
        s1.method4();
        /*
        CompareA:北京
        
        SubClass:上海
        
        SubClass:深圳
        SuperClass:深圳
        SubClass:广州
        CompareA:广州
        CompareB:广州
        
        SubClass:深圳
        */
    }
}
interface CompareA {
    //属性:声明为public static final
    //方法:jdk8之前:只能声明抽象方法
    //方法:jdk8中:静态方法
    public static void method1() {
        System.out.println("CompareA:北京");
    }
    //方法:jdk8中:默认方法
    public default void method2() {
        System.out.println("CompareA:上海");
    }
    public default void method3() {
        System.out.println("CompareA:广州");
    }
    public default void method4() {
        System.out.println("CompareA:深圳");
    }
    //jdk9新特性:定义私有方法
    private void method5(){
        System.out.println("我是接口中定义的私有方法");
    }
}
interface CompareB{
    public default void method3(){
        System.out.println("CompareB:广州");
    }
}
class SubClass extends SuperClass implements CompareA,CompareB{
    @Override
    public void method2() {
        System.out.println("SubClass:上海");
    }
    public void method3(){
        System.out.println("SubClass:广州");
    }
    public void method4(){
        System.out.println("SubClass:深圳");
    }
    public void method(){
        //知识点5:如何在子类(或实现类)中调用父类或接口中被重写的方法
        method4();//调用自己类中的方法
        super.method4(); //调用父类中的
        method3();//调用自己类中的方法
        CompareA.super.method3(); //调用接口CompareA中的默认方法
        CompareB.super.method3(); //调用接口CompareB中的默认方法
    }
}
class SuperClass{
    public void method4(){
        System.out.println("SuperClass:深圳");
    }
}

类的成员 内部类

什么是内部类?
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类(InnerClass),类B则称为外部类(OuterClass)

为什么需要内部类?
具体来说,当一个事物A的内部,还有一个部分需要一个完整的结构B进行描述,而这个内部的完整的结构B又只为外部事物A
提供服务,不在其他地方单独使用,那么整个内部的完整结构B最好使用内部类。总的来说,遵循高内聚、低耦合的面向对象开发原则。

内部类使用举例:
Thread类内部声明了State类,表示线程的生命周期
HashMap类中声明了Node类,表示封装的key和value

内部类的分类:(参考变量的分类)

> 成员内部类:直接声明在外部类的里面。
        > 使用static修饰的:静态的成员内部类
        > 不使用static修饰的:非静态的成员内部类
> 局部内部类:声明在方法内、构造器内、代码块内的内部类
    > 匿名的局部内部类
    > 非匿名的局部内部类

如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为静态内部类,否则声明为非静态内部类

成员内部类的使用特征,概括来讲有如下两种角色:

  • 成员内部类作为类的成员的角色
    • 和外部类不同,Inner class还可以声明为private或protected;
    • 可以调用外部类的结构。(注意:在静态内部类中不能使用外部类的非静态成员)
    • Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
  • 成员内部类作为类的角色
    • 可以在内部定义属性、方法、构造器等结构
    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
    • 可以声明为abstract类 ,因此可以被其它的内部类继承
    • 可以声明为final的,表示不能被继承
    • 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)

注意点:

  1. 外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
  2. 成员内部类可以直接使用外部类的所有成员,包括私有的数据
  3. 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的

创建成员内部类对象

实例化静态内部类

外部类名.静态内部类名 变量 = 外部类名.静态内部类名();
变量.非静态方法();

实例化非静态内部类

外部类名 变量1 = new 外部类();
外部类名.非静态内部类名 变量2 = 变量1.new 非静态内部类名();
变量2.非静态方法();

局部内部类

非匿名局部内部类

[修饰符] class 外部类{
    [修饰符] 返回值类型  方法名(形参列表){
            [final/abstract] class 内部类{
    	}
    }    
}
  • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
    • 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
  • 和成员内部类不同的是,它前面不能有权限修饰符等
  • 局部内部类如同局部变量一样,有作用域
  • 局部内部类中是否能访问外部类的非静态的成员,取决于所在的方法

匿名内部类

new 父类([实参列表]){
    重写方法...
}

关于成员内部类的理解:

从类的角度看:
- 内部可以声明属性、方法、构造器、代码块、内部类等结构
- 此内部类可以声明父类,可以实现接口
- 可以使用final修饰
- 可以使用abstract修饰

从外部类的成员的角度看:
- 在内部可以调用外部类的结构。比如:属性、方法等
- 除了使用public、缺省权限修饰之外,还可以使用private、protected修饰
- 可以使用static修饰

如何创建成员内部类的实例?

首先你需要在外部类的实例内部进行。
先创建外部类的对象,然后通过这个对象来创建内部类的对象。比如,如果有一个外部类Outer和一个内部类Inner

public class Outer {
    class Inner {
    // 内部类的方法和成员
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建外部类对象
        Outer outer = new Outer();
        
        // 通过外部类对象创建内部类对象
        Outer.Inner inner = outer.new Inner();
        
        // 现在你可以使用内部类的方法和成员
        inner.someMethod();
    }
}

如何在成员内部类中调用外部类的结构?

在成员内部类里,

你可以直接访问外部类的非私有成员,就像它们是内部类的一部分一样。

如果你想在内部类中调用外部类的变量或方法

public class Outer {
    private int outerVariable = 10; // 外部类的变量
    class Inner {
        void innerMethod() {
            // 内部类可以直接访问外部类的非私有成员
            System.out.println("外部类的变量: " + outerVariable);
            
            outerMethod(); // 调用外部类的方法
        }

        // 内部类也可以调用外部类的非私有方法
        void outerMethod() {
            System.out.println("外部类的方法被调用了");
        }
    }
}

局部内部类的基本使用


枚举类(自定义 enum 方法 实现接口)

枚举类的理解:
枚举类型本质上也是一种类,只不过是这个类的对象是有限的、固定的几个,不能让用户随意创建

举例:

  • 星期:Monday(星期一)…Sunday(星期天)
  • 性别:Man(男)、Woman(女)
  • 月份:January(1月)…December(12月)
  • 季节:Spring(春节)…Winter(冬天)

开发中的建议:

开发中,如果针对于某个类,其实例是确定个数的。则推荐将此类声明为枚举类。
如果枚举类的实例只有一个,则可以看做是单例的实现方式。

JDK5.0 之前如何自定义枚举类 (了解)

需要程序员自定义枚举类型。

  • 私有化类的构造器,保证不能在类的外部创建其对象
  • 在类的内部创建枚举类的实例。声明为:public static final ,对外暴露这些常量对象
  • 对象如果有实例变量,应该声明为private final(建议,不是必须),并在构造器中初始化
public class SeasonTest {
    public static void main(String[] args) {
//        Season.SPRING = null;

        System.out.println(Season.SPRING);

        System.out.println(Season.SUMMER.getSeasonName());
        System.out.println(Season.SUMMER.getSeasonDesc());
    }
}
//jdk5.0之前定义枚举类的方式
class Season{
    //2. 声明当前类的对象的实例变量,使用private final修饰
    private final String seasonName;//季节的名称
    private final String seasonDesc;//季节的描述

    //1. 私有化类的构造器
    private Season(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //3. 提供实例变量的get方法
    public String getSeasonName() {
        return seasonName;
    }
    public String getSeasonDesc() {
        return seasonDesc;
    }
    //4. 创建当前类的实例,需要使用public static final修饰
    public static final Season SPRING = new Season("春天","春暖花开");
    public static final Season SUMMER = new Season("夏天","夏日炎炎");
    public static final Season AUTUMN = new Season("秋天","秋高气爽");
    public static final Season WINTER = new Season("冬天","白雪皑皑");

    @Override
    public String toString() {
        return "Season{" +
                "seasonName='" + seasonName + '\'' +
                ", seasonDesc='" + seasonDesc + '\'' +
                '}';
    }
}

JDK5.0中使用enum定义枚举类

public class SeasonTest1 {
    public static void main(String[] args) {
//        System.out.println(Season1.SPRING.getClass());
//        System.out.println(Season1.SPRING.getClass().getSuperclass());
//        System.out.println(Season1.SPRING.getClass().getSuperclass().getSuperclass());

        //测试方法
        //1. toString()
        System.out.println(Season1.SPRING);
        System.out.println(Season1.AUTUMN);

        //2. name()
        System.out.println(Season1.SPRING.name());

        //3. vlaues()
        Season1[] values = Season1.values();
        for (int i = 0; i < values.length; i++) {
            System.out.println(values[i]);
        }

        //4. valueOf(String objName):返回当前枚举类中名称为objName的枚举类对象。
        //如果枚举类中不存在objName名称的对象,则报错。
        String objName = "WINTER";
//        objName = "WINTER1";
        Season1 season1 = Season1.valueOf(objName);
        System.out.println(season1);

        //5.ordinal()
        System.out.println(Season1.AUTUMN.ordinal());

        //通过枚举类的对象调用重写接口中的方法
        Season1.SUMMER.show();
    }
}

interface Info{
    void show();
}

//jdk5.0中使用enum关键字定义枚举类
enum Season1 implements Info{
    //1. 必须在枚举类的开头声明多个对象。对象之间使用,隔开
    SPRING("春天","春暖花开"),
    SUMMER("夏天","夏日炎炎"),
    AUTUMN("秋天","秋高气爽"),
    WINTER("冬天","白雪皑皑");

    //2. 声明当前类的对象的实例变量,使用private final修饰
    private final String seasonName;//季节的名称
    private final String seasonDesc;//季节的描述

    //3. 私有化类的构造器
    private Season1(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //4. 提供实例变量的get方法
    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }

    @Override
    public String toString() {
        return "Season1{" +
                "seasonName='" + seasonName + '\'' +
                ", seasonDesc='" + seasonDesc + '\'' +
                '}';
    }

    @Override
    public void show() {
        System.out.println("这是一个季节");
    }
}

Enum中的常用方法:

使用enum关键字定义的枚举类,默认其父类是java.lang.Enum类 。使用enum关键字定义的枚举类,不要再显示的定义其父类。否则报错。

熟悉Enum类中常用的方法

String toString(): 默认返回的是常量名(对象名),可以继续手动重写该方法!
(关注)static 枚举类型[] values():返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值,是一个静态方法
(关注)static 枚举类型 valueOf(String name):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
String name():得到当前枚举常量的名称。建议优先使用toString()。
int ordinal():返回当前枚举常量的次序号,默认从0开始

枚举类实现接口的操作
情况1:枚举类实现接口,在枚举类中重写接口中的抽象方法。当通过不同的枚举类对象调用此方法时,执行的是同一个方法。
情况2:让枚举类的每一个对象重写接口中的抽象方法。当通过不同的枚举类对象调用此方法时,执行的是不同的实现的方法。


注解 常用注解 与 JUnit单元测试

注解(Annotation)是从JDK5.0开始引入,以“@注解名”在代码中存在。例如:

@Override
@Deprecated
@SuppressWarnings(value=”unchecked”)

Annotation 可以像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。还可以添加一些参数值,这些信息被保存在 Annotation 的 “name=value” 对中。

在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码XML配置等。

框架 = 注解 + 反射 + 设计模式

常见的Annotation作用

生成文档相关的注解

@author 标明开发该类模块的作者,多个作者之间使用,分割
@version 标明该类模块的版本
@see 参考转向,也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明,如果没有参数就不能写
@return 对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写

在编译时进行格式检查(JDK内置的三个基本注解)

`@Override`: 限定重写父类方法,该注解只能用于方法

`@Deprecated`: 用于表示所修饰的元素(类,方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择

`@SuppressWarnings`: 抑制编译器警告

跟踪代码依赖性,实现替代配置文件功能

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response) { }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        doGet(request, response);
	}  
}

@Override

  • 用于检测被标记的方法为有效的重写方法,如果不是,则报编译错误!

  • 只能标记在方法上。

  • 它会被编译器程序读取。

@Deprecated

  • 用于表示被标记的数据已经过时,不推荐使用。

  • 可以用于修饰 属性、方法、构造、类、包、局部变量、参数。

  • 它会被编译器程序读取。

@SuppressWarnings

  • 抑制编译警告。当我们不希望看到警告信息的时候,可以使用 SuppressWarnings 注解来抑制警告信息

  • 可以用于修饰类、属性、方法、构造、局部变量、参数

  • 它会被编译器程序读取。

  • 可以指定的警告类型有(了解)

    • all,抑制所有警告
    • unchecked,抑制与未检查的作业相关的警告
    • unused,抑制与未用的程式码及停用的程式码相关的警告
    • deprecation,抑制与淘汰的相关警告
    • nls,抑制与非 nls 字串文字相关的警告
    • null,抑制与空值分析相关的警告
    • rawtypes,抑制与使用 raw 类型相关的警告
    • static-access,抑制与静态存取不正确相关的警告
    • static-method,抑制与可能宣告为 static 的方法相关的警告
    • super,抑制与置换方法相关但不含 super 呼叫的警告

自定义注解
以@SuppressWarnings为参照,进行定义即可

声明自定义注解

【元注解】
【修饰符】 @interface 注解名{
    【成员列表】
}

元注解的理解:
元注解:对现有的注解进行解释说明的注解。

4个元注解:
(1)@Target:用于描述注解的使用范围
可以通过枚举类型ElementType的10个常量对象来指定TYPE,METHOD,CONSTRUCTOR,PACKAGE…

(2)@Retention:用于描述注解的生命周期
可以通过枚举类型RetentionPolicy的3个常量对象来指定 SOURCE(源代码)、CLASS(字节码)、RUNTIME(运行时)
唯有RUNTIME阶段才能被反射读取到。

(3)@Documented:表明这个注解应该被 javadoc工具记录。
(4)@Inherited:允许子类继承父类中的注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}


@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

读取和处理自定义注解

自定义注解必须配上注解的信息处理流程才有意义。

我们自己定义的注解,只能使用反射的代码读取。所以自定义注解的声明周期必须是RetentionPolicy.RUNTIME。

  • 自定义注解可以通过四个元注解@Retention,@Target,@Inherited,@Documented,分别说明它的声明周期,使用位置,是否被继承,是否被生成到API文档中。
  • Annotation 的成员在 Annotation 定义中以无参数有返回值的抽象方法的形式来声明,我们又称为配置参数。返回值类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组
  • 可以使用 default 关键字为抽象方法指定默认返回值
  • 如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式是“方法名 = 返回值”,如果只有一个抽象方法需要赋值,且方法名为value,可以省略“value=”,所以如果注解只有一个抽象方法成员,建议使用方法名value。

使用自定义注解

import java.lang.annotation.*;

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}
import java.lang.annotation.*;

@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String columnName();
    String columnType();
}
@Table("t_stu")
public class Student {
    @Column(columnName = "sid",columnType = "int")
    private int id;
    @Column(columnName = "sname",columnType = "varchar(20)")
    private String name;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
            "id=" + id +
            ", name='" + name + '\'' +
            '}';
    }
}

JUnit单元测试

引入本地JUnit.jar

需要导入的jar包:
junit-4.12.jar
hamcrest-core-1.3.jar

要想能正确的编写单元测试方法,需要满足:

  • 所在的类必须是public的,非抽象的,包含唯一的无参构造器。
  • @Test标记的方法本身必须是public,非抽象的,非静态的,void无返回值,()无参数的。
public class JUnitTest {
    public static void main(String[] args) {
        JUnitTest jUnitTest = new JUnitTest();
        jUnitTest.test2();
    }
    //带参方法 可放到无参测试方法测试
    public int showInfo(String info){
        System.out.println(info);
        return 10;
    }
    @Test
    public void test1(){
        System.out.println("hello");
    }

    @Test
    public void test2(){
        System.out.println("test2");
        int num = showInfo("China");
        System.out.println(num);
    }
    @Test
    public void test3(){
        Scanner scan =new Scanner(System.in);
        System.out.println("请输入一个数值:");
        int num = scan.nextInt();
        System.out.println(num);
    }
}

定义test测试方法模板


包装类

ava提供了两个类型系统,基本数据类型引用数据类型。使用基本数据类型在于效率,要使用只针对对象设计的API或新特性

将基本数据类型装到包装类中 再调用相关的API方法就简单了

Integer Byte Short Float Double Long Character Boolean

自定义包装类

public class MyInteger {
    int value;
    public MyInteger() {
    }
    public MyInteger(int value) {
        this.value = value;
    }
    @Override
    public String toString() {
        return String.valueOf(value);
    }
}

为什么需要转换

  一方面,在有些场景下,需要使用基本数据类型对应的包装类的对象。此时就需要将基本数据类型的变量转换为
   包装类的对象。比如:ArrayList的add(Object obj);Object类的equals(Object obj)
  对于包装类来讲,既然我们使用的是对象,那么对象是不能进行+ - * /等运算的。为了能够进行这些运算,就
   需要将包装类的对象转换为基本数据类型的变量。
包装类缓存对象
Byte-128~127
Short-128~127
Integer-128~127
Long-128~127
Float没有
Double没有
Character0~127
Booleantrue和false

包装类与基本数据类型间的转换
(装箱)基本数据类型 —> 包装类:① 使用包装类的构造器 ② (建议)调用包装类的valueOf(xxx xx)
(拆箱)包装类 —> 基本数据类型:调用包装类的xxxValue()

装箱 把基本数据类型转为包装类对象

Integer obj1 = new Integer(4);//使用构造函数函数
Float f = new Float(4.56);
Long l = new Long(“asdf”);  //NumberFormatException   不推荐

Integer obj2 = Integer.valueOf(4);//使用包装类中的valueOf方法  将基本数据类型转为包装类  推荐

拆箱:把包装类对象拆为基本数据类型

Integer obj = new Integer(4);
int num1 = obj.intValue();

**自动装箱与拆箱 (推荐) ** jdk5.0新特性:自动装箱、自动拆箱。

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

能与自己对应的类型之间才能实现自动装箱与拆箱。

Integer i = 1;
Double d = 1;//错误的,1是int类型

基本数据类型、包装类与字符串间的转换

基本数据类型、包装类 —> String类型:① 调用String的重载的静态方法valueOf(xxx xx) ; ② 基本数据类型的变量 + “”

String类型 —> 基本数据类型、包装类: 调用包装类的静态方法:parseXxx()

基本数据类型转为字符串 调用字符串重载的valueOf()方法

int a = 10;
//String str = a;//错误的

String str = String.valueOf(a);

更直接的方式 隐式转换

int a = 10;
String str = a + "";

字符串转为基本数据类型 parseInt …

**方式1:**除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型,例如:

  • public static int parseInt(String s):将字符串参数转换为对应的int基本类型。
  • public static long parseLong(String s):将字符串参数转换为对应的long基本类型。
  • public static double parseDouble(String s):将字符串参数转换为对应的double基本类型。

**方式2:**字符串转为包装类,然后可以自动拆箱为基本数据类型

  • public static Integer valueOf(String s):将字符串参数转换为对应的Integer包装类,然后可以自动拆箱为int基本类型
  • public static Long valueOf(String s):将字符串参数转换为对应的Long包装类,然后可以自动拆箱为long基本类型
  • public static Double valueOf(String s):将字符串参数转换为对应的Double包装类,然后可以自动拆箱为double基本类型

注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常。

包装类的其它API

数据类型的最大最小值

Integer.MAX_VALUEInteger.MIN_VALUE    
Long.MAX_VALUELong.MIN_VALUE   
Double.MAX_VALUEDouble.MIN_VALUE

字符转大小写

Character.toUpperCase('x');
Character.toLowerCase('X');

整数转进制

Integer.toBinaryString(int i) 
Integer.toHexString(int i)
Integer.toOctalString(int i)

比较的方法

Double.compare(double d1, double d2)
Integer.compare(int x, int y) 

类型转换问题

Integer i = 1000;
double j = 1000;
System.out.println(i==j);//true  会先将i自动拆箱为int,然后根据基本数据类型“自动类型转换”规则,转为double比较

Integer i = 1;
Double d = 1.0
System.out.println(i==d);//编译报错

包装类对象不可变

public class TestExam {
	public static void main(String[] args) {
		int i = 1;
		Integer j = new Integer(2);
		Circle c = new Circle();
		change(i,j,c);
		System.out.println("i = " + i);//1
		System.out.println("j = " + j);//2
		System.out.println("c.radius = " + c.radius);//10.0
	}
	
	/*
	 * 方法的参数传递机制:
	 * (1)基本数据类型:形参的修改完全不影响实参
	 * (2)引用数据类型:通过形参修改对象的属性值,会影响实参的属性值
	 * 这类Integer等包装类对象是“不可变”对象,即一旦修改,就是新对象,和实参就无关了
	 */
	public static void change(int a ,Integer b,Circle c ){
		a += 10;
//		b += 10;//等价于  b = new Integer(b+10);
		c.radius += 10;
		/*c = new Circle();
		c.radius+=10;*/
	}
}
class Circle{
	double radius;
}

debug调试

  1. 为什么需要Debug?
    编好的程序在执行过程中如果出现错误,该如何查找或定位错误呢?简单的代码直接就可以看出来,
    但如果代码比较复杂,就需要借助程序调试工具(Debug)来查找错误了。

  2. Debug的步骤
    1、添加断点
    2、启动调试
    3、单步执行
    4、观察变量和执行流程,找到并解决问题

主要掌握 源码进入 强制进入 跳到下一个断点 结束这几个就行


  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值