精华内容
下载资源
问答
  • Java基础知识面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:11:27
    文章目录Java概述何为编程什么是Javajdk1.5之后的三大版本JVM、JRE和JDK的关系什么是跨平台性?原理是什么Java语言有哪些特点什么是字节码?采用字节码的最大好处是什么什么是Java程序的主类?应用程序和小程序的...

    Java面试总结(2021优化版)已发布在个人微信公众号【技术人成长之路】,优化版首先修正了读者反馈的部分答案存在的错误,同时根据最新面试总结,删除了低频问题,添加了一些常见面试题,对文章进行了精简优化,欢迎大家关注!😊😊

    【技术人成长之路】,助力技术人成长!更多精彩文章第一时间在公众号发布哦!

    文章目录

    Java面试总结汇总,整理了包括Java基础知识,集合容器,并发编程,JVM,常用开源框架Spring,MyBatis,数据库,中间件等,包含了作为一个Java工程师在面试中需要用到或者可能用到的绝大部分知识。欢迎大家阅读,本人见识有限,写的博客难免有错误或者疏忽的地方,还望各位大佬指点,在此表示感激不尽。文章持续更新中…

    序号内容链接地址
    1Java基础知识面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390612
    2Java集合容器面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588551
    3Java异常面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390689
    4并发编程面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104863992
    5JVM面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390752
    6Spring面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397516
    7Spring MVC面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397427
    8Spring Boot面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397299
    9Spring Cloud面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397367
    10MyBatis面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/101292950
    11Redis面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/103522351
    12MySQL数据库面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104778621
    13消息中间件MQ与RabbitMQ面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588612
    14Dubbo面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390006
    15Linux面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588679
    16Tomcat面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397665
    17ZooKeeper面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397719
    18Netty面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104391081
    19架构设计&分布式&数据结构与算法面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/105870730

    Java概述

    何为编程

    编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。

    为了使计算机能够理解人的意图,人类就必须要将需解决的问题的思路、方法、和手段通过计算机能够理解的形式告诉计算机,使得计算机能够根据人的指令一步一步去工作,完成某种特定的任务。这种人和计算机之间交流的过程就是编程。

    什么是Java

    Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。

    jdk1.5之后的三大版本

    • Java SE(J2SE,Java 2 Platform Standard Edition,标准版)
      Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为Java EE和Java ME提供基础。
    • Java EE(J2EE,Java 2 Platform Enterprise Edition,企业版)
      Java EE 以前称为 J2EE。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web2.0应用程序。2018年2月,Eclipse 宣布正式将 JavaEE 更名为 JakartaEE
    • Java ME(J2ME,Java 2 Platform Micro Edition,微型版)
      Java ME 以前称为 J2ME。Java ME 为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。Java ME 包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于 Java ME 规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。

    JVM、JRE和JDK的关系

    JVM
    Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。

    JRE
    Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包

    如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。

    JDK
    Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等

    JVM&JRE&JDK关系图

    什么是跨平台性?原理是什么

    所谓跨平台性,是指java语言编写的程序,一次编译后,可以在多个系统平台上运行。

    实现原理:Java程序是通过java虚拟机在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序。

    Java语言有哪些特点

    简单易学(Java语言的语法与C语言和C++语言很接近)

    面向对象(封装,继承,多态)

    平台无关性(Java虚拟机实现平台无关性)

    支持网络编程并且很方便(Java语言诞生本身就是为简化网络编程设计的)

    支持多线程(多线程机制使应用程序在同一时间并行执行多项任)

    健壮性(Java语言的强类型机制、异常处理、垃圾的自动收集等)

    安全性

    什么是字节码?采用字节码的最大好处是什么

    字节码:Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。

    采用字节码的好处

    Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。

    先看下java中的编译器和解释器

    Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行,这就是上面提到的Java的特点的编译与解释并存的解释。

    Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
    

    什么是Java程序的主类?应用程序和小程序的主类有何不同?

    一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主类是指包含main()方法的类。而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序的主类不一定要求是public类,但小程序的主类要求必须是public类。主类是Java程序执行的入口点。

    Java应用程序与小程序之间有那些差别?

    简单说应用程序是从主线程启动(也就是main()方法)。applet小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟flash的小游戏类似。

    Java和C++的区别

    我知道很多人没学过C++,但是面试官就是没事喜欢拿咱们Java和C++比呀!没办法!!!就算没学过C++,也要记下来!

    • 都是面向对象的语言,都支持封装、继承和多态
    • Java不提供指针来直接访问内存,程序内存更加安全
    • Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
    • Java有自动内存管理机制,不需要程序员手动释放无用内存

    Oracle JDK 和 OpenJDK 的对比

    1. Oracle JDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次;

    2. OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的;

    3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题;

    4. 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能;

    5. Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;

    6. Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。

    基础语法

    数据类型

    Java有哪些数据类型

    定义:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。

    分类

    • 基本数据类型
      • 数值型
        • 整数类型(byte,short,int,long)
        • 浮点类型(float,double)
      • 字符型(char)
      • 布尔型(boolean)
    • 引用数据类型
      • 类(class)
      • 接口(interface)
      • 数组([])

    Java基本数据类型图

    switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上

    在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。

    用最有效率的方法计算 2 乘以 8

    2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次方)。

    Math.round(11.5) 等于多少?Math.round(-11.5)等于多少

    Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5 然后进行下取整。

    float f=3.4;是否正确

    不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成 float f =3.4F;。

    short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗

    对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int型,需要强制转换类型才能赋值给 short 型。

    而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换。

    编码

    Java语言采用何种编码方案?有何特点?

    Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。

    注释

    什么Java注释

    定义:用于解释说明程序的文字

    分类

    • 单行注释
      格式: // 注释文字
    • 多行注释
      格式: /* 注释文字 */
    • 文档注释
      格式:/** 注释文字 */

    作用

    在程序中,尤其是复杂的程序中,适当地加入注释可以增加程序的可读性,有利于程序的修改、调试和交流。注释的内容在程序编译的时候会被忽视,不会产生目标代码,注释的部分不会对程序的执行结果产生任何影响。

    注意事项:多行和文档注释都不能嵌套使用。

    访问修饰符

    访问修饰符 public,private,protected,以及不写(默认)时的区别

    定义:Java中,可以使用访问修饰符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

    分类

    private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
    default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
    protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
    public : 对所有类可见。使用对象:类、接口、变量、方法

    访问修饰符图

    运算符

    &和&&的区别

    &运算符有两种用法:(1)按位与;(2)逻辑与。

    &&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true 整个表达式的值才是 true。&&之所以称为短路运算,是因为如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。

    注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

    关键字

    Java 有没有 goto

    goto 是 Java 中的保留字,在目前版本的 Java 中没有使用。

    final 有什么用?

    用于修饰类、属性和方法;

    • 被final修饰的类不可以被继承
    • 被final修饰的方法不可以被重写
    • 被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的

    final finally finalize区别

    • final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表
      示该变量是一个常量不能被重新赋值。
    • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块
      中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
    • finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调
      用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的
      最后判断。

    this关键字的用法

    this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。

    this的用法在java中大体可以分为3种:

    1.普通的直接引用,this相当于是指向当前对象本身。

    2.形参与成员名字重名,用this来区分:

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

    3.引用本类的构造函数

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

    super关键字的用法

    super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。

    super也有三种用法:

    1.普通的直接引用

    与this类似,super相当于是指向当前对象的父类的引用,这样就可以用super.xxx来引用父类的成员。

    2.子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分

    class Person{
        protected String name;
     
        public Person(String name) {
            this.name = name;
        }
     
    }
     
    class Student extends Person{
        private String name;
     
        public Student(String name, String name1) {
            super(name);
            this.name = name1;
        }
     
        public void getInfo(){
            System.out.println(this.name);      //Child
            System.out.println(super.name);     //Father
        }
     
    }
    
    public class Test {
        public static void main(String[] args) {
           Student s1 = new Student("Father","Child");
           s1.getInfo();
     
        }
    }
    

    3.引用父类构造函数

    3、引用父类构造函数

    • super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
    • this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。

    this与super的区别

    • super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)
    • this:它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)
    • super()和this()类似,区别是,super()在子类中调用父类的构造方法,this()在本类内调用本类的其它构造方法。
    • super()和this()均需放在构造方法内第一行。
    • 尽管可以用this调用一个构造器,但却不能调用两个。
    • this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
    • this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
    • 从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。

    static存在的主要意义

    static的主要意义是在于创建独立于具体对象的域变量或者方法。以致于即使没有创建对象,也能使用属性和调用方法

    static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

    为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。

    static的独特之处

    1、被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享

    怎么理解 “被类的实例对象所共享” 这句话呢?就是说,一个类的静态成员,它是属于大伙的【大伙指的是这个类的多个对象实例,我们都知道一个类可以创建多个实例!】,所有的类对象共享的,不像成员变量是自个的【自个指的是这个类的单个实例对象】…我觉得我已经讲的很通俗了,你明白了咩?

    2、在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。

    3、static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!

    4、被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。

    static应用场景

    因为static是被类的实例对象所共享,因此如果某个成员变量是被所有对象所共享的,那么这个成员变量就应该定义为静态变量

    因此比较常见的static应用场景有:

    1、修饰成员变量 2、修饰成员方法 3、静态代码块 4、修饰类【只能修饰内部类也就是静态内部类】 5、静态导包

    static注意事项

    1、静态只能访问静态。 2、非静态既可以访问非静态的,也可以访问静态的。

    流程控制语句

    break ,continue ,return 的区别及作用

    break 跳出总上一层循环,不再执行循环(结束当前的循环体)

    continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)

    return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)

    在 Java 中,如何跳出当前的多重嵌套循环

    在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出外层循环。例如:

    public static void main(String[] args) {
        ok:
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                System.out.println("i=" + i + ",j=" + j);
                if (j == 5) {
                    break ok;
                }
    
            }
        }
    }
    

    面向对象

    面向对象概述

    面向对象和面向过程的区别

    面向过程

    优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。

    缺点:没有面向对象易维护、易复用、易扩展

    面向对象

    优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护

    缺点:性能比面向过程低

    面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。

    面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。

    面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。

    面向对象三大特性

    面向对象的特征有哪些方面

    面向对象的特征主要有以下几个方面

    抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

    封装

    封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。

    继承

    继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。

    关于继承如下 3 点请记住:

    1. 子类拥有父类非 private 的属性和方法。

    2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。

    3. 子类可以用自己的方式实现父类的方法。(以后介绍)。

    多态

    所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

    在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。

    其中Java 面向对象编程三大特性:封装 继承 多态

    封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。

    继承:继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承可以提高代码复用性。继承是多态的前提。

    关于继承如下 3 点请记住

    1. 子类拥有父类非 private 的属性和方法。

    2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。

    3. 子类可以用自己的方式实现父类的方法。

    多态性:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。

    在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。

    方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。

    一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:

    • 方法重写(子类继承父类并重写父类中已有的或抽象的方法);
    • 对象造型(用父类型引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。

    什么是多态机制?Java语言是如何实现多态的?

    所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

    多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

    多态的实现

    Java实现多态有三个必要条件:继承、重写、向上转型。

    继承:在多态中必须存在有继承关系的子类和父类。

    重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。

    向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

    只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

    对于Java而言,它多态的实现机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。

    面向对象五大基本原则是什么(可选)

    • 单一职责原则SRP(Single Responsibility Principle)
      类的功能要单一,不能包罗万象,跟杂货铺似的。
    • 开放封闭原则OCP(Open-Close Principle)
      一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
    • 里式替换原则LSP(the Liskov Substitution Principle LSP)
      子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
    • 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
      高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。
    • 接口分离原则ISP(the Interface Segregation Principle ISP)
      设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。

    类与接口

    抽象类和接口的对比

    抽象类是用来捕捉子类的通用特性的。接口是抽象方法的集合。

    从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

    相同点

    • 接口和抽象类都不能实例化
    • 都位于继承的顶端,用于被其他实现或继承
    • 都包含抽象方法,其子类都必须覆写这些抽象方法

    不同点

    参数抽象类接口
    声明抽象类使用abstract关键字声明接口使用interface关键字声明
    实现子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现子类使用implements关键字来实现接口。它需要提供接口中所有声明的方法的实现
    构造器抽象类可以有构造器接口不能有构造器
    访问修饰符抽象类中的方法可以是任意访问修饰符接口方法默认修饰符是public。并且不允许定义为 private 或者 protected
    多继承一个类最多只能继承一个抽象类一个类可以实现多个接口
    字段声明抽象类的字段声明可以是任意的接口的字段默认都是 static 和 final 的

    备注:Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。

    现在,我们可以为接口提供默认实现的方法了,并且不用强制子类来实现它。

    接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:

    • 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
    • 选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。

    普通类和抽象类有哪些区别?

    • 普通类不能包含抽象方法,抽象类可以包含抽象方法。
    • 抽象类不能直接实例化,普通类可以直接实例化。

    抽象类能使用 final 修饰吗?

    不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类

    创建一个对象用什么关键字?对象实例与对象引用有何不同?

    new关键字,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)

    变量与方法

    成员变量与局部变量的区别有哪些

    变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。从本质上讲,变量其实是内存中的一小块区域

    成员变量:方法外部,类内部定义的变量

    局部变量:类的方法中的变量。

    成员变量和局部变量的区别

    作用域

    成员变量:针对整个类有效。
    局部变量:只在某个范围内有效。(一般指的就是方法,语句体内)

    存储位置

    成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。
    局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。

    生命周期

    成员变量:随着对象的创建而存在,随着对象的消失而消失
    局部变量:当方法调用完,或者语句结束后,就自动释放。

    初始值

    成员变量:有默认初始值。

    局部变量:没有默认初始值,使用前必须赋值。

    使用原则

    在使用变量时需要遵循的原则为:就近原则
    首先在局部范围找,有就使用;接着在成员位置找。

    在Java中定义一个不做事且没有参数的构造方法的作用

    Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

    在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?

    帮助子类做初始化工作。

    一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?

    主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。

    构造方法有哪些特性?

    名字与类名相同;

    没有返回值,但不能用void声明构造函数;

    生成类的对象时自动执行,无需调用。

    静态变量和实例变量区别

    静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。

    实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。

    静态变量与普通变量区别

    static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

    还有一点就是static成员变量的初始化顺序按照定义的顺序进行初始化。

    静态方法和实例方法有何不同?

    静态方法和实例方法的区别主要体现在两个方面:

    1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
    2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制

    在一个静态方法内调用一个非静态成员为什么是非法的?

    由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。

    什么是方法的返回值?返回值的作用是什么?

    方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!

    内部类

    什么是内部类?

    在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。

    内部类的分类有哪些

    内部类可以分为四种:成员内部类、局部内部类、匿名内部类和静态内部类

    静态内部类

    定义在类内部的静态类,就是静态内部类。

    public class Outer {
    
        private static int radius = 1;
    
        static class StaticInner {
            public void visit() {
                System.out.println("visit outer static  variable:" + radius);
            }
        }
    }
    

    静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;静态内部类的创建方式,new 外部类.静态内部类(),如下:

    Outer.StaticInner inner = new Outer.StaticInner();
    inner.visit();
    
    成员内部类

    定义在类内部,成员位置上的非静态类,就是成员内部类。

    public class Outer {
    
        private static  int radius = 1;
        private int count =2;
        
         class Inner {
            public void visit() {
                System.out.println("visit outer static  variable:" + radius);
                System.out.println("visit outer   variable:" + count);
            }
        }
    }
    

    成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,它的创建方式外部类实例.new 内部类(),如下:

    Outer outer = new Outer();
    Outer.Inner inner = outer.new Inner();
    inner.visit();
    
    局部内部类

    定义在方法中的内部类,就是局部内部类。

    public class Outer {
    
        private  int out_a = 1;
        private static int STATIC_b = 2;
    
        public void testFunctionClass(){
            int inner_c =3;
            class Inner {
                private void fun(){
                    System.out.println(out_a);
                    System.out.println(STATIC_b);
                    System.out.println(inner_c);
                }
            }
            Inner  inner = new Inner();
            inner.fun();
        }
        public static void testStaticFunctionClass(){
            int d =3;
            class Inner {
                private void fun(){
                    // System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量
                    System.out.println(STATIC_b);
                    System.out.println(d);
                }
            }
            Inner  inner = new Inner();
            inner.fun();
        }
    }
    

    定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。局部内部类的创建方式,在对应方法内,new 内部类(),如下:

     public static void testStaticFunctionClass(){
        class Inner {
        }
        Inner  inner = new Inner();
     }
    
    匿名内部类

    匿名内部类就是没有名字的内部类,日常开发中使用的比较多。

    public class Outer {
    
        private void test(final int i) {
            new Service() {
                public void method() {
                    for (int j = 0; j < i; j++) {
                        System.out.println("匿名内部类" );
                    }
                }
            }.method();
        }
     }
     //匿名内部类必须继承或实现一个已有的接口 
     interface Service{
        void method();
    }
    

    除了没有名字,匿名内部类还有以下特点:

    • 匿名内部类必须继承一个抽象类或者实现一个接口。
    • 匿名内部类不能定义任何静态成员和静态方法。
    • 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
    • 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

    匿名内部类创建方式:

    new/接口{ 
      //匿名内部类实现部分
    }
    

    内部类的优点

    我们为什么要使用内部类呢?因为它有以下优点:

    • 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
    • 内部类不为同一包的其他类所见,具有很好的封装性;
    • 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
    • 匿名内部类可以很方便的定义回调。

    内部类有哪些应用场景

    1. 一些多算法场合
    2. 解决一些非面向对象的语句块。
    3. 适当使用内部类,使得代码更加灵活和富有扩展性。
    4. 当某个类除了它的外部类,不再被其他的类使用时。

    局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?

    局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final呢?它内部原理是什么呢?

    先看这段代码:

    public class Outer {
    
        void outMethod(){
            final int a =10;
            class Inner {
                void innerMethod(){
                    System.out.println(a);
                }
    
            }
        }
    }
    

    以上例子,为什么要加final呢?是因为生命周期不一致, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。

    内部类相关,看程序说出运行结果

    public class Outer {
        private int age = 12;
    
        class Inner {
            private int age = 13;
            public void print() {
                int age = 14;
                System.out.println("局部变量:" + age);
                System.out.println("内部类变量:" + this.age);
                System.out.println("外部类变量:" + Outer.this.age);
            }
        }
    
        public static void main(String[] args) {
            Outer.Inner in = new Outer().new Inner();
            in.print();
        }
    
    }
    

    运行结果:

    局部变量:14
    内部类变量:13
    外部类变量:12
    

    重写与重载

    构造器(constructor)是否可被重写(override)

    构造器不能被继承,因此不能被重写,但可以被重载。

    重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

    方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

    重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分

    重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。

    对象相等判断

    == 和 equals 的区别是什么

    == : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)

    equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

    情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。

    情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。

    举个例子:

    public class test1 {
        public static void main(String[] args) {
            String a = new String("ab"); // a 为一个引用
            String b = new String("ab"); // b为另一个引用,对象的内容一样
            String aa = "ab"; // 放在常量池中
            String bb = "ab"; // 从常量池中查找
            if (aa == bb) // true
                System.out.println("aa==bb");
            if (a == b) // false,非同一对象
                System.out.println("a==b");
            if (a.equals(b)) // true
                System.out.println("aEQb");
            if (42 == 42.0) { // true
                System.out.println("true");
            }
        }
    }
    

    说明:

    • String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
    • 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。

    hashCode 与 equals (重要)

    HashSet如何检查重复

    两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?

    hashCode和equals方法的关系

    面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”

    hashCode()介绍

    hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。

    散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

    为什么要有 hashCode

    我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode

    当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

    hashCode()与equals()的相关规定

    如果两个对象相等,则hashcode一定也是相同的

    两个对象相等,对两个对象分别调用equals方法都返回true

    两个对象有相同的hashcode值,它们也不一定是相等的

    因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖

    hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

    对象的相等与指向他们的引用相等,两者有什么不同?

    对象的相等 比的是内存中存放的内容是否相等而 引用相等 比较的是他们指向的内存地址是否相等。

    值传递

    当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递

    是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的

    为什么 Java 中只有值传递

    首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。

    Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。

    下面通过 3 个例子来给大家说明

    example 1

    public static void main(String[] args) {
        int num1 = 10;
        int num2 = 20;
    
        swap(num1, num2);
    
        System.out.println("num1 = " + num1);
        System.out.println("num2 = " + num2);
    }
    
    public static void swap(int a, int b) {
        int temp = a;
        a = b;
        b = temp;
    
        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }
    

    结果

    a = 20
    b = 10
    num1 = 10
    num2 = 20
    

    解析

    img

    在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。

    通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.

    example 2

        public static void main(String[] args) {
            int[] arr = { 1, 2, 3, 4, 5 };
            System.out.println(arr[0]);
            change(arr);
            System.out.println(arr[0]);
        }
    
        public static void change(int[] array) {
            // 将数组的第一个元素变为0
            array[0] = 0;
        }
    

    结果

    1
    0
    

    解析

    img

    array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。

    通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。

    很多程序设计语言(特别是,C++和Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。

    example 3

    public class Test {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Student s1 = new Student("小张");
            Student s2 = new Student("小李");
            Test.swap(s1, s2);
            System.out.println("s1:" + s1.getName());
            System.out.println("s2:" + s2.getName());
        }
    
        public static void swap(Student x, Student y) {
            Student temp = x;
            x = y;
            y = temp;
            System.out.println("x:" + x.getName());
            System.out.println("y:" + y.getName());
        }
    }
    

    结果

    x:小李
    y:小张
    s1:小张
    s2:小李
    

    解析

    交换之前:

    img

    交换之后:

    img

    通过上面两张图可以很清晰的看出: 方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝

    总结

    Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的。

    下面再总结一下Java中方法参数的使用情况:

    • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型》
    • 一个方法可以改变一个对象参数的状态。
    • 一个方法不能让对象参数引用一个新的对象。

    值传递和引用传递有什么区别

    值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。

    引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。

    Java包

    JDK 中常用的包有哪些

    • java.lang:这个是系统的基础类;
    • java.io:这里面是所有输入输出有关的类,比如文件操作等;
    • java.nio:为了完善 io 包中的功能,提高 io 包中性能而写的一个新包;
    • java.net:这里面是与网络有关的类;
    • java.util:这个是系统辅助类,特别是集合类;
    • java.sql:这个是数据库操作的类。

    import java和javax有什么区别

    刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。

    所以,实际上java和javax没有区别。这都是一个名字。

    IO流

    java 中 IO 流分为几种?

    • 按照流的流向分,可以分为输入流和输出流;
    • 按照操作单元划分,可以划分为字节流和字符流;
    • 按照流的角色划分为节点流和处理流。

    Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。

    • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
    • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

    按操作方式分类结构图:

    IO-操作方式分类

    按操作对象分类结构图:

    IO-操作对象分类

    BIO,NIO,AIO 有什么区别?

    简答

    • BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
    • NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
    • AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

    详细回答

    • BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
    • NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
    • AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。

    Files的常用方法都有哪些?

    • Files. exists():检测文件路径是否存在。
    • Files. createFile():创建文件。
    • Files. createDirectory():创建文件夹。
    • Files. delete():删除一个文件或目录。
    • Files. copy():复制文件。
    • Files. move():移动文件。
    • Files. size():查看文件个数。
    • Files. read():读取文件。
    • Files. write():写入文件。

    反射

    什么是反射机制?

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

    静态编译和动态编译

    • **静态编译:**在编译时确定类型,绑定对象
    • **动态编译:**运行时确定类型,绑定对象

    反射机制优缺点

    • 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
    • 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。

    反射机制的应用场景有哪些?

    反射是框架设计的灵魂。

    在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。

    举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性

    Java获取反射的三种方法

    1.通过new对象实现反射机制 2.通过路径实现反射机制 3.通过类名实现反射机制

    public class Student {
        private int id;
        String name;
        protected boolean sex;
        public float score;
    }
    
    public class Get {
        //获取反射机制三种方式
        public static void main(String[] args) throws ClassNotFoundException {
            //方式一(通过建立对象)
            Student stu = new Student();
            Class classobj1 = stu.getClass();
            System.out.println(classobj1.getName());
            //方式二(所在通过路径-相对路径)
            Class classobj2 = Class.forName("fanshe.Student");
            System.out.println(classobj2.getName());
            //方式三(通过类名)
            Class classobj3 = Student.class;
            System.out.println(classobj3.getName());
        }
    }
    

    网络编程

    网络编程的面试题可以查看我的这篇文章重学TCP/IP协议和三次握手四次挥手,内容不仅包括TCP/IP协议和三次握手四次挥手的知识,还包括计算机网络体系结构,HTTP协议,get请求和post请求区别,session和cookie的区别等,欢迎大家阅读。

    常用API

    String相关

    字符型常量和字符串常量的区别

    1. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符
    2. 含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
    3. 占内存大小 字符常量只占两个字节 字符串常量占若干个字节(至少一个字符结束标志)

    什么是字符串常量池?

    字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。

    String 是最基本的数据类型吗

    不是。Java 中的基本数据类型只有 8 个 :byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(referencetype),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。

    这是很基础的东西,但是很多初学者却容易忽视,Java 的 8 种基本数据类型中不包括 String,基本数据类型中用来描述文本数据的是 char,但是它只能表示单个字符,比如 ‘a’,‘好’ 之类的,如果要描述一段文本,就需要用多个 char 类型的变量,也就是一个 char 类型数组,比如“你好” 就是长度为2的数组 char[] chars = {‘你’,‘好’};

    但是使用数组过于麻烦,所以就有了 String,String 底层就是一个 char 类型的数组,只是使用的时候开发者不需要直接操作底层数组,用更加简便的方式即可完成对字符串的使用。

    String有哪些特性

    • 不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。

    • 常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。

    • final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。

    String为什么是不可变的吗?

    简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以:

    /** The value is used for character storage. */
    private final char value[];
    

    String真的是不可变的吗?

    我觉得如果别人问这个问题的话,回答不可变就可以了。 下面只是给大家看两个有代表性的例子:

    1) String不可变但不代表引用不可以变

    String str = "Hello";
    str = str + " World";
    System.out.println("str=" + str);
    

    结果:

    str=Hello World
    

    解析:

    实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。

    2) 通过反射是可以修改所谓的“不可变”对象

    // 创建字符串"Hello World", 并赋给引用s
    String s = "Hello World";
    
    System.out.println("s = " + s); // Hello World
    
    // 获取String类中的value字段
    Field valueFieldOfString = String.class.getDeclaredField("value");
    
    // 改变value属性的访问权限
    valueFieldOfString.setAccessible(true);
    
    // 获取s对象上的value属性的值
    char[] value = (char[]) valueFieldOfString.get(s);
    
    // 改变value所引用的数组中的第5个字符
    value[5] = '_';
    
    System.out.println("s = " + s); // Hello_World
    

    结果:

    s = Hello World
    s = Hello_World
    

    解析:

    用反射可以访问私有成员, 然后反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。

    是否可以继承 String 类

    String 类是 final 类,不可以被继承。

    String str="i"与 String str=new String(“i”)一样吗?

    不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。

    String s = new String(“xyz”);创建了几个字符串对象

    两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。

    String str1 = "hello"; //str1指向静态区
    String str2 = new String("hello");  //str2指向堆上的对象
    String str3 = "hello";
    String str4 = new String("hello");
    System.out.println(str1.equals(str2)); //true
    System.out.println(str2.equals(str4)); //true
    System.out.println(str1 == str3); //true
    System.out.println(str1 == str2); //false
    System.out.println(str2 == str4); //false
    System.out.println(str2 == "hello"); //false
    str2 = str1;
    System.out.println(str2 == "hello"); //true
    

    如何将字符串反转?

    使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。

    示例代码:

    // StringBuffer reverse
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer. append("abcdefg");
    System. out. println(stringBuffer. reverse()); // gfedcba
    // StringBuilder reverse
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder. append("abcdefg");
    System. out. println(stringBuilder. reverse()); // gfedcba
    

    数组有没有 length()方法?String 有没有 length()方法

    数组没有 length()方法 ,有 length 的属性。String 有 length()方法。JavaScript中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java 混淆。

    String 类的常用方法都有那些?

    • indexOf():返回指定字符的索引。
    • charAt():返回指定索引处的字符。
    • replace():字符串替换。
    • trim():去除字符串两端空白。
    • split():分割字符串,返回一个分割后的字符串数组。
    • getBytes():返回字符串的 byte 类型数组。
    • length():返回字符串长度。
    • toLowerCase():将字符串转成小写字母。
    • toUpperCase():将字符串转成大写字符。
    • substring():截取字符串。
    • equals():字符串比较。

    在使用 HashMap 的时候,用 String 做 key 有什么好处?

    HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。

    String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的

    可变性

    String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。

    线程安全性

    String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

    性能

    每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

    对于三者使用的总结

    如果要操作少量的数据用 = String

    单线程操作字符串缓冲区 下操作大量数据 = StringBuilder

    多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

    Date相关

    包装类相关

    自动装箱与拆箱

    装箱:将基本类型用它们对应的引用类型包装起来;

    拆箱:将包装类型转换为基本数据类型;

    int 和 Integer 有什么区别

    Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。

    Java 为每个原始类型提供了包装类型:

    原始类型: boolean,char,byte,short,int,long,float,double

    包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

    Integer a= 127 与 Integer b = 127相等吗

    对于对象引用类型:==比较的是对象的内存地址。
    对于基本数据类型:==比较的是值。

    如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false

    public static void main(String[] args) {
        Integer a = new Integer(3);
        Integer b = 3;  // 将3自动装箱成Integer类型
        int c = 3;
        System.out.println(a == b); // false 两个引用没有引用同一对象
        System.out.println(a == c); // true a自动拆箱成int类型再和c比较
        System.out.println(b == c); // true
    
        Integer a1 = 128;
        Integer b1 = 128;
        System.out.println(a1 == b1); // false
    
        Integer a2 = 127;
        Integer b2 = 127;
        System.out.println(a2 == b2); // true
    }
    

    常用工具类库

    单元测试

    日志

    展开全文
  • Java面试题大全(2020版)

    万次阅读 多人点赞 2019-11-26 11:59:06
    发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家有帮助哈~ 本套Java面试题大全,全的不能再全,哈哈~ 一、Java 基础 1. JDK 和 JRE 有什么区别? JDK:Java ...

    发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家有帮助哈~

    本套Java面试题大全,全的不能再全,哈哈~

    博主已将以下这些面试题整理成了一个Java面试手册,是PDF版的。

    关注博主的微信公众号:Java团长,然后回复“面试手册”即可获取~

    一、Java 基础

    1. JDK 和 JRE 有什么区别?

    • JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
    • JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。

    具体来说 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。

    2. == 和 equals 的区别是什么?

    == 解读

    对于基本类型和引用类型 == 的作用效果是不同的,如下所示:

    • 基本类型:比较的是值是否相同;
    • 引用类型:比较的是引用是否相同;

    代码示例:

    String x = "string";
    String y = "string";
    String z = new String("string");
    System.out.println(x==y); // true
    System.out.println(x==z); // false
    System.out.println(x.equals(y)); // true
    System.out.println(x.equals(z)); // true

    代码解读:因为 x 和 y 指向的是同一个引用,所以 == 也是 true,而 new String()方法则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果都为 true。

    equals 解读

    equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。看下面的代码就明白了。

    首先来看默认情况下 equals 比较一个有相同值的对象,代码如下:

    class Cat {
        public Cat(String name) {
            this.name = name;
        }
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    Cat c1 = new Cat("王磊");
    Cat c2 = new Cat("王磊");
    System.out.println(c1.equals(c2)); // false

    输出结果出乎我们的意料,竟然是 false?这是怎么回事,看了 equals 源码就知道了,源码如下:

    public boolean equals(Object obj) {
        return (this == obj);
    }

    原来 equals 本质上就是 ==。

    那问题来了,两个相同值的 String 对象,为什么返回的是 true?代码如下:

    String s1 = new String("老王");
    String s2 = new String("老王");
    System.out.println(s1.equals(s2)); // true

    同样的,当我们进入 String 的 equals 方法,找到了答案,代码如下:

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

    原来是 String 重写了 Object 的 equals 方法,把引用比较改成了值比较。

    总结 :== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。

    3. 两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

    不对,两个对象的 hashCode()相同,equals()不一定 true。

    代码示例:

    String str1 = "通话";
    String str2 = "重地";
    System.out.println(String.format("str1:%d | str2:%d",  str1.hashCode(),str2.hashCode()));
    System.out.println(str1.equals(str2));

    执行的结果:

    str1:1179395 | str2:1179395

    false

    代码解读:很显然“通话”和“重地”的 hashCode() 相同,然而 equals() 则为 false,因为在散列表中,hashCode()相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。

    4. final 在 java 中有什么作用?

    • final 修饰的类叫最终类,该类不能被继承。
    • final 修饰的方法不能被重写。
    • final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

    5. java 中的 Math.round(-1.5) 等于多少?

    等于 -1,因为在数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负 0.5 是直接舍弃。

    6. String 属于基础的数据类型吗?

    String 不属于基础类型,基础类型有 8 种:byte、boolean、char、short、int、float、long、double,而 String 属于对象。

    7. java 中操作字符串都有哪些类?它们之间有什么区别?

    操作字符串的类有:String、StringBuffer、StringBuilder。

    String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

    StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

    8. String str="i"与 String str=new String("i")一样吗?

    不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String("i") 则会被分到堆内存中。

    9. 如何将字符串反转?

    使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。

    示例代码:

    // StringBuffer reverse
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append("abcdefg");
    System.out.println(stringBuffer.reverse()); // gfedcba
    // StringBuilder reverse
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append("abcdefg");
    System.out.println(stringBuilder.reverse()); // gfedcba

    10. String 类的常用方法都有那些?

    • indexOf():返回指定字符的索引。
    • charAt():返回指定索引处的字符。
    • replace():字符串替换。
    • trim():去除字符串两端空白。
    • split():分割字符串,返回一个分割后的字符串数组。
    • getBytes():返回字符串的 byte 类型数组。
    • length():返回字符串长度。
    • toLowerCase():将字符串转成小写字母。
    • toUpperCase():将字符串转成大写字符。
    • substring():截取字符串。
    • equals():字符串比较。

    11. 抽象类必须要有抽象方法吗?

    不需要,抽象类不一定非要有抽象方法。

    示例代码:

    abstract class Cat {
        public static void sayHi() {
            System.out.println("hi~");
        }
    }

    上面代码,抽象类并没有抽象方法但完全可以正常运行。

    12. 普通类和抽象类有哪些区别?

    • 普通类不能包含抽象方法,抽象类可以包含抽象方法。
    • 抽象类不能直接实例化,普通类可以直接实例化。

    13. 抽象类能使用 final 修饰吗?

    不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类,如下图所示,编辑器也会提示错误信息:

    14. 接口和抽象类有什么区别?

    • 实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
    • 构造函数:抽象类可以有构造函数;接口不能有。
    • main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
    • 实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
    • 访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。

    15. java 中 IO 流分为几种?

    按功能来分:输入流(input)、输出流(output)。

    按类型来分:字节流和字符流。

    字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。

    16. BIO、NIO、AIO 有什么区别?

    • BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
    • NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
    • AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

    17. Files的常用方法都有哪些?

    • Files.exists():检测文件路径是否存在。
    • Files.createFile():创建文件。
    • Files.createDirectory():创建文件夹。
    • Files.delete():删除一个文件或目录。
    • Files.copy():复制文件。
    • Files.move():移动文件。
    • Files.size():查看文件个数。
    • Files.read():读取文件。
    • Files.write():写入文件。

    二、容器

    18. java 容器都有哪些?

    常用容器的图录:

    19. Collection 和 Collections 有什么区别?

    • java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
    • Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

    20. List、Set、Map 之间的区别是什么?

    21. HashMap 和 Hashtable 有什么区别?

    • hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。
    • hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。
    • hashMap允许空键值,而hashTable不允许。

    22. 如何决定使用 HashMap 还是 TreeMap?

    对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。

    23. 说一下 HashMap 的实现原理?

    HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 

    HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

    当我们往Hashmap中put元素时,首先根据key的hashcode重新计算hash值,根绝hash值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。

    需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)

    24. 说一下 HashSet 的实现原理?

    • HashSet底层由HashMap实现
    • HashSet的值存放于HashMap的key上
    • HashMap的value统一为PRESENT

    25. ArrayList 和 LinkedList 的区别是什么?

    最明显的区别是 ArrrayList底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。

    26. 如何实现数组和 List 之间的转换?

    • List转换成为数组:调用ArrayList的toArray方法。
    • 数组转换成为List:调用Arrays的asList方法。

    27. ArrayList 和 Vector 的区别是什么?

    • Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。 
    • ArrayList比Vector快,它因为有同步,不会过载。 
    • ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。

    28. Array 和 ArrayList 有何区别?

    • Array可以容纳基本类型和对象,而ArrayList只能容纳对象。 
    • Array是指定大小的,而ArrayList大小是固定的。 
    • Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。

    29. 在 Queue 中 poll()和 remove()有什么区别?

    poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。

    30. 哪些集合类是线程安全的?

    • vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
    • statck:堆栈类,先进后出。
    • hashtable:就比hashmap多了个线程安全。
    • enumeration:枚举,相当于迭代器。

    31. 迭代器 Iterator 是什么?

    迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。

    32. Iterator 怎么使用?有什么特点?

    Java中的Iterator功能比较简单,并且只能单向移动:

    (1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。

    (2) 使用next()获得序列中的下一个元素。

    (3) 使用hasNext()检查序列中是否还有元素。

    (4) 使用remove()将迭代器新返回的元素删除。

    Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。

    33. Iterator 和 ListIterator 有什么区别?

    • Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。 
    • Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。 
    • ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

     三、多线程

    35. 并行和并发有什么区别?

    • 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
    • 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
    • 在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群。

    所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。

    36. 线程和进程的区别?

    简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。

    37. 守护线程是什么?

    守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程。

    38. 创建线程有哪几种方式?

    ①. 继承Thread类创建线程类

    • 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
    • 创建Thread子类的实例,即创建了线程对象。
    • 调用线程对象的start()方法来启动该线程。

    ②. 通过Runnable接口创建线程类

    • 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
    • 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
    • 调用线程对象的start()方法来启动该线程。

    ③. 通过Callable和Future创建线程

    • 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
    • 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
    • 使用FutureTask对象作为Thread对象的target创建并启动新线程。
    • 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

    39. 说一下 runnable 和 callable 有什么区别?

    有点深的问题了,也看出一个Java程序员学习知识的广度。

    • Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
    • Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

    40. 线程有哪些状态?

    线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。

    • 创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
    • 就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
    • 运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
    • 阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
    • 死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪   

    41. sleep() 和 wait() 有什么区别?

    sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。

    wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。

    42. notify()和 notifyAll()有什么区别?

    • 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
    • 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。
    • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

    43. 线程的 run()和 start()有什么区别?

    每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。

    start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。

    run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。

    44. 创建线程池有哪几种方式?

    ①. newFixedThreadPool(int nThreads)

    创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。

    ②. newCachedThreadPool()

    创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。

    ③. newSingleThreadExecutor()

    这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。

    ④. newScheduledThreadPool(int corePoolSize)

    创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。

    45. 线程池都有哪些状态?

    线程池有5种状态:Running、ShutDown、Stop、Tidying、Terminated。

    线程池各个状态切换框架图:

    46. 线程池中 submit()和 execute()方法有什么区别?

    • 接收的参数不一样
    • submit有返回值,而execute没有
    • submit方便Exception处理

    47. 在 java 程序中怎么保证多线程的运行安全?

    线程安全在三个方面体现:

    • 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
    • 可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
    • 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。

    48. 多线程锁的升级原理是什么?

    在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。

    锁升级的图示过程: 

    49. 什么是死锁?

    死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操作系统层面的一个错误,是进程死锁的简称,最早在 1965 年由 Dijkstra 在研究银行家算法时提出的,它是计算机操作系统乃至整个并发程序设计领域最难处理的问题之一。

    50. 怎么防止死锁?

    死锁的四个必要条件:

    • 互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
    • 请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放
    • 不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
    • 环路等待条件:是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系

    这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不满足,就不会发生死锁。

    理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和 解除死锁。

    所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确 定资源的合理分配算法,避免进程永久占据系统资源。

    此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。

    51. ThreadLocal 是什么?有哪些使用场景?

    线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。

    52.说一下 synchronized 底层实现原理?

    synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。

    Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

    • 普通同步方法,锁是当前实例对象
    • 静态同步方法,锁是当前类的class对象
    • 同步方法块,锁是括号里面的对象

    53. synchronized 和 volatile 的区别是什么?

    • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
    • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
    • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
    • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
    • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

    54. synchronized 和 Lock 有什么区别?

    • 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
    • synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
    • synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
    • 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
    • synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);
    • Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

    55. synchronized 和 ReentrantLock 区别是什么?

    synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上: 

    • ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁 
    • ReentrantLock可以获取各种锁的信息
    • ReentrantLock可以灵活地实现多路通知 

    另外,二者的锁机制其实也是不一样的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word。

    56. 说一下 atomic 的原理?

    Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。

    Atomic系列的类中的核心方法都会调用unsafe类中的几个本地方法。我们需要先知道一个东西就是Unsafe类,全名为:sun.misc.Unsafe,这个类包含了大量的对C代码的操作,包括很多直接内存分配以及原子操作的调用,而它之所以标记为非安全的,是告诉你这个里面大量的方法调用都会存在安全隐患,需要小心使用,否则会导致严重的后果,例如在通过unsafe分配内存的时候,如果自己指定某些区域可能会导致一些类似C++一样的指针越界到其他进程的问题。


    四、反射

    57. 什么是反射?

    反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力

    Java反射:

    在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法

    Java反射机制主要提供了以下功能:

    • 在运行时判断任意一个对象所属的类。
    • 在运行时构造任意一个类的对象。
    • 在运行时判断任意一个类所具有的成员变量和方法。
    • 在运行时调用任意一个对象的方法。 

    58. 什么是 java 序列化?什么情况下需要序列化?

    简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。

    什么情况下需要序列化:

    a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
    b)当你想用套接字在网络上传送对象的时候;
    c)当你想通过RMI传输对象的时候;

    59. 动态代理是什么?有哪些应用?

    动态代理:

    当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。

    动态代理的应用:

    • Spring的AOP
    • 加事务
    • 加权限
    • 加日志

    60. 怎么实现动态代理?

    首先必须定义一个接口,还要有一个InvocationHandler(将实现接口的类的对象传递给它)处理类。再有一个工具类Proxy(习惯性将其称为代理类,因为调用他的newInstance()可以产生代理对象,其实他只是一个产生代理对象的工具类)。利用到InvocationHandler,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实例化产生代理对象,最后返回。


    五、对象拷贝

    61. 为什么要使用克隆?

    想对一个对象进行处理,又想保留原有的数据进行接下来的操作,就需要克隆了,Java语言中克隆针对的是类的实例。

    62. 如何实现对象克隆?

    有两种方式:

    1). 实现Cloneable接口并重写Object类中的clone()方法;

    2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下:

    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    
    public class MyUtil {
    
        private MyUtil() {
            throw new AssertionError();
        }
    
        @SuppressWarnings("unchecked")
        public static <T extends Serializable> T clone(T obj) throws Exception {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bout);
            oos.writeObject(obj);
    
            ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bin);
            return (T) ois.readObject();
    
            // 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
            // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
        }
    }

    下面是测试代码:

    
    import java.io.Serializable;
    
    /**
     * 人类
     * @author nnngu
     *
     */
    class Person implements Serializable {
        private static final long serialVersionUID = -9102017020286042305L;
    
        private String name;    // 姓名
        private int age;        // 年龄
        private Car car;        // 座驾
    
        public Person(String name, int age, Car car) {
            this.name = name;
            this.age = age;
            this.car = car;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public Car getCar() {
            return car;
        }
    
        public void setCar(Car car) {
            this.car = car;
        }
    
        @Override
        public String toString() {
            return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
        }
    
    }
    
    /**
     * 小汽车类
     * @author nnngu
     *
     */
    class Car implements Serializable {
        private static final long serialVersionUID = -5713945027627603702L;
    
        private String brand;       // 品牌
        private int maxSpeed;       // 最高时速
    
        public Car(String brand, int maxSpeed) {
            this.brand = brand;
            this.maxSpeed = maxSpeed;
        }
    
        public String getBrand() {
            return brand;
        }
    
        public void setBrand(String brand) {
            this.brand = brand;
        }
    
        public int getMaxSpeed() {
            return maxSpeed;
        }
    
        public void setMaxSpeed(int maxSpeed) {
            this.maxSpeed = maxSpeed;
        }
    
        @Override
        public String toString() {
            return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
        }
    
    }
    class CloneTest {
    
        public static void main(String[] args) {
            try {
                Person p1 = new Person("郭靖", 33, new Car("Benz", 300));
                Person p2 = MyUtil.clone(p1);   // 深度克隆
                p2.getCar().setBrand("BYD");
                // 修改克隆的Person对象p2关联的汽车对象的品牌属性
                // 原来的Person对象p1关联的汽车不会受到任何影响
                // 因为在克隆Person对象时其关联的汽车对象也被克隆了
                System.out.println(p1);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是好过把问题留到运行时。

    63. 深拷贝和浅拷贝区别是什么?

    • 浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝(例:assign())
    • 深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝(例:JSON.parse()和JSON.stringify(),但是此方法无法复制函数类型)

    六、Java Web

    64. jsp 和 servlet 有什么区别?

    1. jsp经编译后就变成了Servlet.(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类)
    2. jsp更擅长表现于页面显示,servlet更擅长于逻辑控制。
    3. Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到。
    4. Jsp是Servlet的一种简化,使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中的Java脚本如何镶嵌到一个类中,由Jsp容器完成。而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应。

    65. jsp 有哪些内置对象?作用分别是什么?

    JSP有9个内置对象:

    • request:封装客户端的请求,其中包含来自GET或POST请求的参数;
    • response:封装服务器对客户端的响应;
    • pageContext:通过该对象可以获取其他对象;
    • session:封装用户会话的对象;
    • application:封装服务器运行环境的对象;
    • out:输出服务器响应的输出流对象;
    • config:Web应用的配置对象;
    • page:JSP页面本身(相当于Java程序中的this);
    • exception:封装页面抛出异常的对象。

    66. 说一下 jsp 的 4 种作用域?

    JSP中的四种作用域包括page、request、session和application,具体来说:

    • page代表与一个页面相关的对象和属性。
    • request代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个Web组件;需要在页面显示的临时数据可以置于此作用域。
    • session代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。
    • application代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。

    67. session 和 cookie 有什么区别?

    • 由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session.典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识。在服务端保存Session的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放 Session。
    • 思考一下服务端如何识别特定的客户?这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。
    • Cookie其实还可以用在一些方便用户的场景下,设想你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到Cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。这也是Cookie名称的由来,给用户的一点甜头。所以,总结一下:Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。

    68. 说一下 session 的工作原理?

    其实session是一个存在服务器上的类似于一个散列表格的文件。里面存有我们需要的信息,在我们需要用的时候可以从里面取出来。类似于一个大号的map吧,里面的键存储的是用户的sessionid,用户向服务器发送请求的时候会带上这个sessionid。这时就可以从中取出对应的值了。

    69. 如果客户端禁止 cookie 能实现 session 还能用吗?

    Cookie与 Session,一般认为是两个独立的东西,Session采用的是在服务器端保持状态的方案,而Cookie采用的是在客户端保持状态的方案。但为什么禁用Cookie就不能得到Session呢?因为Session是用Session ID来确定当前对话所对应的服务器Session,而Session ID是通过Cookie来传递的,禁用Cookie相当于失去了Session ID,也就得不到Session了。

    假定用户关闭Cookie的情况下使用Session,其实现途径有以下几种:

    1. 设置php.ini配置文件中的“session.use_trans_sid = 1”,或者编译时打开打开了“--enable-trans-sid”选项,让PHP自动跨页传递Session ID。
    2. 手动通过URL传值、隐藏表单传递Session ID。
    3. 用文件、数据库等形式保存Session ID,在跨页过程中手动调用。

    70. spring mvc 和 struts 的区别是什么?

    • 拦截机制的不同

    Struts2是类级别的拦截,每次请求就会创建一个Action,和Spring整合时Struts2的ActionBean注入作用域是原型模式prototype,然后通过setter,getter吧request数据注入到属性。Struts2中,一个Action对应一个request,response上下文,在接收参数时,可以通过属性接收,这说明属性参数是让多个方法共享的。Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了,只能设计为多例。

    SpringMVC是方法级别的拦截,一个方法对应一个Request上下文,所以方法直接基本上是独立的,独享request,response数据。而每个方法同时又何一个url对应,参数的传递是直接注入到方法中的,是方法所独有的。处理结果通过ModeMap返回给框架。在Spring整合时,SpringMVC的Controller Bean默认单例模式Singleton,所以默认对所有的请求,只会创建一个Controller,有应为没有共享的属性,所以是线程安全的,如果要改变默认的作用域,需要添加@Scope注解修改。

    Struts2有自己的拦截Interceptor机制,SpringMVC这是用的是独立的Aop方式,这样导致Struts2的配置文件量还是比SpringMVC大。

    • 底层框架的不同

    Struts2采用Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)则采用Servlet实现。Filter在容器启动之后即初始化;服务停止以后坠毁,晚于Servlet。Servlet在是在调用时初始化,先于Filter调用,服务停止后销毁。

    • 性能方面

    Struts2是类级别的拦截,每次请求对应实例一个新的Action,需要加载所有的属性值注入,SpringMVC实现了零配置,由于SpringMVC基于方法的拦截,有加载一次单例模式bean注入。所以,SpringMVC开发效率和性能高于Struts2。

    • 配置方面

    spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高。

    71. 如何避免 sql 注入?

    1. PreparedStatement(简单又有效的方法)
    2. 使用正则表达式过滤传入的参数
    3. 字符串过滤
    4. JSP中调用该函数检查是否包函非法字符
    5. JSP页面判断代码

    72. 什么是 XSS 攻击,如何避免?

    XSS攻击又称CSS,全称Cross Site Script  (跨站脚本攻击),其原理是攻击者向有XSS漏洞的网站中输入恶意的 HTML 代码,当用户浏览该网站时,这段 HTML 代码会自动执行,从而达到攻击的目的。XSS 攻击类似于 SQL 注入攻击,SQL注入攻击中以SQL语句作为用户输入,从而达到查询/修改/删除数据的目的,而在xss攻击中,通过插入恶意脚本,实现对用户游览器的控制,获取用户的一些信息。 XSS是 Web 程序中常见的漏洞,XSS 属于被动式且用于客户端的攻击方式。

    XSS防范的总体思路是:对输入(和URL参数)进行过滤,对输出进行编码。

    73. 什么是 CSRF 攻击,如何避免?

    CSRF(Cross-site request forgery)也被称为 one-click attack或者 session riding,中文全称是叫跨站请求伪造。一般来说,攻击者通过伪造用户的浏览器的请求,向访问一个用户自己曾经认证访问过的网站发送出去,使目标网站接收并误以为是用户的真实操作而去执行命令。常用于盗取账号、转账、发送虚假消息等。攻击者利用网站对请求的验证漏洞而实现这样的攻击行为,网站能够确认请求来源于用户的浏览器,却不能验证请求是否源于用户的真实意愿下的操作行为。

    如何避免:

    1. 验证 HTTP Referer 字段

    HTTP头中的Referer字段记录了该 HTTP 请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,而如果黑客要对其实施 CSRF
    攻击,他一般只能在他自己的网站构造请求。因此,可以通过验证Referer值来防御CSRF 攻击。

    2. 使用验证码

    关键操作页面加上验证码,后台收到请求后通过判断验证码可以防御CSRF。但这种方法对用户不太友好。

    3. 在请求地址中添加token并验证

    CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于cookie中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有token或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于session之中,然后在每次请求时把token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。
    对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue。
    而对于 POST 请求来说,要在 form 的最后加上 <input type="hidden" name="csrftoken" value="tokenvalue"/>,这样就把token以参数的形式加入请求了。

    4. 在HTTP 头中自定义属性并验证

    这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。


    七、异常

    74. throw 和 throws 的区别?

    throws是用来声明一个方法可能抛出的所有异常信息,throws是将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理。而throw则是指抛出的一个具体的异常类型。

    75. final、finally、finalize 有什么区别?

    • final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
    • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
    • finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System的gc()方法的时候,由垃圾回收器调用finalize(),回收垃圾。 

    76. try-catch-finally 中哪个部分可以省略?

    答:catch 可以省略

    原因:

    更为严格的说法其实是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。

    理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。

    至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。

    77. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

    答:会执行,在 return 前执行。

    代码示例1:

    
    /*
     * java面试题--如果catch里面有return语句,finally里面的代码还会执行吗?
     */
    public class FinallyDemo2 {
        public static void main(String[] args) {
            System.out.println(getInt());
        }
    
        public static int getInt() {
            int a = 10;
            try {
                System.out.println(a / 0);
                a = 20;
            } catch (ArithmeticException e) {
                a = 30;
                return a;
                /*
                 * return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就形成了
                 * 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
                 * 再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30
                 */
            } finally {
                a = 40;
            }
    
    //      return a;
        }
    }

    执行结果:30

    代码示例2:

    
    package com.java_02;
    
    /*
     * java面试题--如果catch里面有return语句,finally里面的代码还会执行吗?
     */
    public class FinallyDemo2 {
        public static void main(String[] args) {
            System.out.println(getInt());
        }
    
        public static int getInt() {
            int a = 10;
            try {
                System.out.println(a / 0);
                a = 20;
            } catch (ArithmeticException e) {
                a = 30;
                return a;
                /*
                 * return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就形成了
                 * 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
                 * 再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30
                 */
            } finally {
                a = 40;
                return a; //如果这样,就又重新形成了一条返回路径,由于只能通过1个return返回,所以这里直接返回40
            }
    
    //      return a;
        }
    }

    执行结果:40

    78. 常见的异常类有哪些?

    • NullPointerException:当应用程序试图访问空对象时,则抛出该异常。
    • SQLException:提供关于数据库访问错误或其他错误信息的异常。
    • IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 
    • NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
    • FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。
    • IOException:当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。
    • ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
    • ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常。
    • IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。
    • ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。 
    • NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常。
    • NoSuchMethodException:无法找到某一特定方法时,抛出该异常。
    • SecurityException:由安全管理器抛出的异常,指示存在安全侵犯。
    • UnsupportedOperationException:当不支持请求的操作时,抛出该异常。
    • RuntimeExceptionRuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。

    八、网络

    79. http 响应码 301 和 302 代表的是什么?有什么区别?

    答:301,302 都是HTTP状态的编码,都代表着某个URL发生了转移。

    区别: 

    • 301 redirect: 301 代表永久性转移(Permanently Moved)。
    • 302 redirect: 302 代表暂时性转移(Temporarily Moved )。 

    80. forward 和 redirect 的区别?

    Forward和Redirect代表了两种请求转发方式:直接转发和间接转发。

    直接转发方式(Forward),客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于每个信息资源是共享的。

    间接转发方式(Redirect)实际是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。

    举个通俗的例子:

    直接转发就相当于:“A找B借钱,B说没有,B去找C借,借到借不到都会把消息传递给A”;

    间接转发就相当于:"A找B借钱,B说没有,让A去找C借"。

    81. 简述 tcp 和 udp的区别?

    • TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
    • TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
    • Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
    • UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
    • 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
    • TCP对系统资源要求较多,UDP对系统资源要求较少。

    82. tcp 为什么要三次握手,两次不行吗?为什么?

    为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤。

    如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认。

    83. 说一下 tcp 粘包是怎么产生的?

    ①. 发送方产生粘包

    采用TCP协议传输数据的客户端与服务器经常是保持一个长连接的状态(一次连接发一次数据不存在粘包),双方在连接不断开的情况下,可以一直传输数据;但当发送的数据包过于的小时,那么TCP协议默认的会启用Nagle算法,将这些较小的数据包进行合并发送(缓冲区数据发送是一个堆压的过程);这个合并过程就是在发送缓冲区中进行的,也就是说数据发送出来它已经是粘包的状态了。

    ②. 接收方产生粘包

    接收方采用TCP协议接收数据时的过程是这样的:数据到底接收方,从网络模型的下方传递至传输层,传输层的TCP协议处理是将其放置接收缓冲区,然后由应用层来主动获取(C语言用recv、read等函数);这时会出现一个问题,就是我们在程序中调用的读取数据函数不能及时的把缓冲区中的数据拿出来,而下一个数据又到来并有一部分放入的缓冲区末尾,等我们读取数据时就是一个粘包。(放数据的速度 > 应用层拿数据速度) 

    84. OSI 的七层模型都有哪些?

    1. 应用层:网络服务与最终用户的一个接口。
    2. 表示层:数据的表示、安全、压缩。
    3. 会话层:建立、管理、终止会话。
    4. 传输层:定义传输数据的协议端口号,以及流控和差错校验。
    5. 网络层:进行逻辑地址寻址,实现不同网络之间的路径选择。
    6. 数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验等功能。
    7. 物理层:建立、维护、断开物理连接。

    85. get 和 post 请求有哪些区别?

    • GET在浏览器回退时是无害的,而POST会再次提交请求。
    • GET产生的URL地址可以被Bookmark,而POST不可以。
    • GET请求会被浏览器主动cache,而POST不会,除非手动设置。
    • GET请求只能进行url编码,而POST支持多种编码方式。
    • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
    • GET请求在URL中传送的参数是有长度限制的,而POST么有。
    • 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
    • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
    • GET参数通过URL传递,POST放在Request body中。

    86. 如何实现跨域?

    方式一:图片ping或script标签跨域

    图片ping常用于跟踪用户点击页面或动态广告曝光次数。 
    script标签可以得到从其他来源数据,这也是JSONP依赖的根据。 

    方式二:JSONP跨域

    JSONP(JSON with Padding)是数据格式JSON的一种“使用模式”,可以让网页从别的网域要数据。根据 XmlHttpRequest 对象受到同源策略的影响,而利用 <script>元素的这个开放策略,网页可以得到从其他来源动态产生的JSON数据,而这种使用模式就是所谓的 JSONP。用JSONP抓到的数据并不是JSON,而是任意的JavaScript,用 JavaScript解释器运行而不是用JSON解析器解析。所有,通过Chrome查看所有JSONP发送的Get请求都是js类型,而非XHR。 

    缺点:

    • 只能使用Get请求
    • 不能注册success、error等事件监听函数,不能很容易的确定JSONP请求是否失败
    • JSONP是从其他域中加载代码执行,容易受到跨站请求伪造的攻击,其安全性无法确保

    方式三:CORS

    Cross-Origin Resource Sharing(CORS)跨域资源共享是一份浏览器技术的规范,提供了 Web 服务从不同域传来沙盒脚本的方法,以避开浏览器的同源策略,确保安全的跨域数据传输。现代浏览器使用CORS在API容器如XMLHttpRequest来减少HTTP请求的风险来源。与 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。服务器一般需要增加如下响应头的一种或几种:

    Access-Control-Allow-Origin: *
    Access-Control-Allow-Methods: POST, GET, OPTIONS
    Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
    Access-Control-Max-Age: 86400

    跨域请求默认不会携带Cookie信息,如果需要携带,请配置下述参数:

    "Access-Control-Allow-Credentials": true
    // Ajax设置
    "withCredentials": true

    方式四:window.name+iframe

    window.name通过在iframe(一般动态创建i)中加载跨域HTML文件来起作用。然后,HTML文件将传递给请求者的字符串内容赋值给window.name。然后,请求者可以检索window.name值作为响应。

    • iframe标签的跨域能力;
    • window.name属性值在文档刷新后依旧存在的能力(且最大允许2M左右)。

    每个iframe都有包裹它的window,而这个window是top window的子窗口。contentWindow属性返回<iframe>元素的Window对象。你可以使用这个Window对象来访问iframe的文档及其内部DOM。

    <!-- 
     下述用端口 
     10000表示:domainA
     10001表示:domainB
    -->
    
    <!-- localhost:10000 -->
    <script>
      var iframe = document.createElement('iframe');
      iframe.style.display = 'none'; // 隐藏
    
      var state = 0; // 防止页面无限刷新
      iframe.onload = function() {
          if(state === 1) {
              console.log(JSON.parse(iframe.contentWindow.name));
              // 清除创建的iframe
              iframe.contentWindow.document.write('');
              iframe.contentWindow.close();
              document.body.removeChild(iframe);
          } else if(state === 0) {
              state = 1;
              // 加载完成,指向当前域,防止错误(proxy.html为空白页面)
              // Blocked a frame with origin "http://localhost:10000" from accessing a cross-origin frame.
              iframe.contentWindow.location = 'http://localhost:10000/proxy.html';
          }
      };
    
      iframe.src = 'http://localhost:10001';
      document.body.appendChild(iframe);
    </script>
    
    <!-- localhost:10001 -->
    <!DOCTYPE html>
    ...
    <script>
      window.name = JSON.stringify({a: 1, b: 2});
    </script>
    </html>
    

    方式五:window.postMessage()

    HTML5新特性,可以用来向其他所有的 window 对象发送消息。需要注意的是我们必须要保证所有的脚本执行完才发送 MessageEvent,如果在函数执行的过程中调用了它,就会让后面的函数超时无法执行。

    下述代码实现了跨域存储localStorage

    <!-- 
     下述用端口 
     10000表示:domainA
     10001表示:domainB
    -->
    
    <!-- localhost:10000 -->
    <iframe src="http://localhost:10001/msg.html" name="myPostMessage" style="display:none;">
    </iframe>
    
    <script>
      function main() {
          LSsetItem('test', 'Test: ' + new Date());
          LSgetItem('test', function(value) {
              console.log('value: ' + value);
          });
          LSremoveItem('test');
      }
    
      var callbacks = {};
      window.addEventListener('message', function(event) {
          if (event.source === frames['myPostMessage']) {
              console.log(event)
              var data = /^#localStorage#(\d+)(null)?#([\S\s]*)/.exec(event.data);
              if (data) {
                  if (callbacks[data[1]]) {
                      callbacks[data[1]](data[2] === 'null' ? null : data[3]);
                  }
                  delete callbacks[data[1]];
              }
          }
      }, false);
    
      var domain = '*';
      // 增加
      function LSsetItem(key, value) {
          var obj = {
              setItem: key,
              value: value
          };
          frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
      }
      // 获取
      function LSgetItem(key, callback) {
          var identifier = new Date().getTime();
          var obj = {
              identifier: identifier,
              getItem: key
          };
          callbacks[identifier] = callback;
          frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
      }
      // 删除
      function LSremoveItem(key) {
          var obj = {
              removeItem: key
          };
          frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
      }
    </script>
    
    <!-- localhost:10001 -->
    <script>
      window.addEventListener('message', function(event) {
        console.log('Receiver debugging', event);
        if (event.origin == 'http://localhost:10000') {
          var data = JSON.parse(event.data);
          if ('setItem' in data) {
            localStorage.setItem(data.setItem, data.value);
          } else if ('getItem' in data) {
            var gotItem = localStorage.getItem(data.getItem);
            event.source.postMessage(
              '#localStorage#' + data.identifier +
              (gotItem === null ? 'null#' : '#' + gotItem),
              event.origin
            );
          } else if ('removeItem' in data) {
            localStorage.removeItem(data.removeItem);
          }
        }
      }, false);
    </script>

    注意Safari一下,会报错:

    Blocked a frame with origin “http://localhost:10001” from accessing a frame with origin “http://localhost:10000“. Protocols, domains, and ports must match.

    避免该错误,可以在Safari浏览器中勾选开发菜单==>停用跨域限制。或者只能使用服务器端转存的方式实现,因为Safari浏览器默认只支持CORS跨域请求。

    方式六:修改document.domain跨子域

    前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域,所以只能跨子域

    在根域范围内,允许把domain属性的值设置为它的上一级域。例如,在”aaa.xxx.com”域内,可以把domain设置为 “xxx.com” 但不能设置为 “xxx.org” 或者”com”。

    现在存在两个域名aaa.xxx.com和bbb.xxx.com。在aaa下嵌入bbb的页面,由于其document.name不一致,无法在aaa下操作bbb的js。可以在aaa和bbb下通过js将document.name = 'xxx.com';设置一致,来达到互相访问的作用。

    方式七:WebSocket

    WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很棒的实现。相关文章,请查看:WebSocket、WebSocket-SockJS

    需要注意:WebSocket对象不支持DOM 2级事件侦听器,必须使用DOM 0级语法分别定义各个事件。

    方式八:代理

    同源策略是针对浏览器端进行的限制,可以通过服务器端来解决该问题

    DomainA客户端(浏览器) ==> DomainA服务器 ==> DomainB服务器 ==> DomainA客户端(浏览器)

    来源:blog.csdn.net/ligang2585116/article/details/73072868

    87.说一下 JSONP 实现原理?

    jsonp 即 json+padding,动态创建script标签,利用script标签的src属性可以获取任何域下的js脚本,通过这个特性(也可以说漏洞),服务器端不在返货json格式,而是返回一段调用某个函数的js代码,在src中进行了调用,这样实现了跨域。


    九、设计模式

    88. 说一下你熟悉的设计模式?

    参考:常用的设计模式汇总,超详细!

    89. 简单工厂和抽象工厂有什么区别?

    简单工厂模式

    这个模式本身很简单而且使用在业务较简单的情况下。一般用于小项目或者具体产品很少扩展的情况(这样工厂类才不用经常更改)。

    它由三种角色组成:

    • 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不同,产生具体的工厂产品。如例子中的Driver类。
    • 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。由接口或者抽象类来实现。如例中的Car接口。
    • 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现,如例子中的Benz、Bmw类。

    来用类图来清晰的表示下的它们之间的关系:

    抽象工厂模式:

    先来认识下什么是产品族: 位于不同产品等级结构中,功能相关联的产品组成的家族。

    图中的BmwCar和BenzCar就是两个产品树(产品层次结构);而如图所示的BenzSportsCar和BmwSportsCar就是一个产品族。他们都可以放到跑车家族中,因此功能有所关联。同理BmwBussinessCar和BenzBusinessCar也是一个产品族。

    可以这么说,它和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且抽象工厂模式是三个里面最为抽象、最具一般性的。抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象。

    而且使用抽象工厂模式还要满足一下条件:

    1. 系统中有多个产品族,而系统一次只可能消费其中一族产品
    2. 同属于同一个产品族的产品以其使用。

    来看看抽象工厂模式的各个角色(和工厂方法的如出一辙):

    • 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
    • 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。
    • 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
    • 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

    十、Spring / Spring MVC

    90. 为什么要使用 spring?

    1.简介

    • 目的:解决企业应用开发的复杂性
    • 功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
    • 范围:任何Java应用

    简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

    2.轻量 

    从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。

    3.控制反转  

    Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。

    4.面向切面  

    Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。

    5.容器

    Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。

    6.框架

    Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。

    所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。

    91. 解释一下什么是 aop?

    AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

    而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

    使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

    92. 解释一下什么是 ioc?

    IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”。

    1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先提出了IOC 这个概念。对于面向对象设计及编程的基本思想,前面我们已经讲了很多了,不再赘述,简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。

    IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。如下图:

    大家看到了吧,由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。

    我们再来做个试验:把上图中间的IOC容器拿掉,然后再来看看这套系统:

    我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度。所以,如果真能实现IOC容器,对于系统开发而言,这将是一件多么美好的事情,参与开发的每一成员只要实现自己的类就可以了,跟别人没有任何关系!

    我们再来看看,控制反转(IOC)到底为什么要起这么个名字?我们来对比一下:

    软件系统在没有引入IOC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。

    软件系统在引入IOC容器之后,这种情形就完全改变了,如图3所示,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。

    通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

    93. spring 有哪些主要模块?

    Spring框架至今已集成了20多个模块。这些模块主要被分如下图所示的核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。

    更多信息:howtodoinjava.com/java-spring-framework-tutorials/

    94. spring 常用的注入方式有哪些?

    Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:

    1. 构造方法注入
    2. setter注入
    3. 基于注解的注入

    95. spring 中的 bean 是线程安全的吗?

    Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究。

    96. spring 支持几种 bean 的作用域?

    当通过spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:

    • singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
    • prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
    • request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
    • session:对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
    • globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效

    其中比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实例,然后返回给程序。在这种情况下,Spring容器仅仅使用new 关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。

    如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。

    97. spring 自动装配 bean 有哪些方式?

    Spring容器负责创建应用程序中的bean同时通过ID来协调这些对象之间的关系。作为开发人员,我们需要告诉Spring要创建哪些bean并且如何将其装配到一起。

    spring中bean装配有两种方式:

    • 隐式的bean发现机制和自动装配
    • 在java代码或者XML中进行显示配置

    当然这些方式也可以配合使用。

    98. spring 事务实现方式有哪些?

    1. 编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
    2. 基于 TransactionProxyFactoryBean 的声明式事务管理
    3. 基于 @Transactional 的声明式事务管理
    4. 基于 Aspectj AOP 配置事务

    99. 说一下 spring 的事务隔离?

    事务隔离级别指的是一个事务对数据的修改与另一个并行的事务的隔离程度,当多个事务同时访问相同数据时,如果没有采取必要的隔离机制,就可能发生以下问题:

    • 脏读:一个事务读到另一个事务未提交的更新数据。
    • 幻读:例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样。
    • 不可重复读:比方说在同一个事务中先后执行两条一模一样的select语句,期间在此次事务中没有执行过任何DDL语句,但先后得到的结果不一致,这就是不可重复读。

    100. 说一下 spring mvc 运行流程?

    Spring MVC运行流程图:

    Spring运行流程描述:

    1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;

    2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;

    3. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter;(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法)

    4.  提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

    • HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
    • 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
    • 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
    • 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中

    5.  Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;

    6.  根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;

    7. ViewResolver 结合Model和View,来渲染视图;

    8. 将渲染结果返回给客户端。

    101. spring mvc 有哪些组件?

    Spring MVC的核心组件:

    1. DispatcherServlet:中央控制器,把请求给转发到具体的控制类
    2. Controller:具体处理请求的控制器
    3. HandlerMapping:映射处理器,负责映射中央处理器转发给controller时的映射策略
    4. ModelAndView:服务层返回的数据和视图层的封装类
    5. ViewResolver:视图解析器,解析具体的视图
    6. Interceptors :拦截器,负责拦截我们定义的请求然后做处理工作

    102. @RequestMapping 的作用是什么?

    RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

    RequestMapping注解有六个属性,下面我们把她分成三类进行说明。

    value, method:

    • value:指定请求的实际地址,指定的地址可以是URI Template 模式(后面将会说明);
    • method:指定请求的method类型, GET、POST、PUT、DELETE等;

    consumes,produces

    • consumes:指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
    • produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;

    params,headers

    • params: 指定request中必须包含某些参数值是,才让该方法处理。
    • headers:指定request中必须包含某些指定的header值,才能让该方法处理请求。

    103. @Autowired 的作用是什么?

    《@Autowired用法详解》


    未完待续......


    欢迎大家关注我的公众号:Java团长,后续面试题更新之后可以在第一时间获取~

    展开全文
  • java返回值的使用

    万次阅读 2018-11-13 16:22:04
    java返回值的使用 一、返回一般数据类型 八大基本数据类型之一(byte,char,short,int,long,float,double,boolean) Object Object是java中所有类的始祖,所有类都默认继承了Object。Object类型的变量可以指向...

    java返回值的使用

    一、返回一般数据类型

    八大基本数据类型之一(byte,char,short,int,long,float,double,boolean)

    Object

    java.lang.Object

    Object是java中所有类的始祖,所有类都默认继承了Object。Object类型的变量可以指向任意类型的对象,但Object类型的变量只能作为实际对象的持有者,若要对对象的内容进行具体操作必须明确该对象的原始类型并进行相应的类型转换。

    方法

    Object 类中的equals 方法:

     public boolean equals(Object obj) {
            return (this == obj);
        }

      在Java规范中,对 equals 方法的使用必须遵循以下几个原则:

      ①、自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。

      ②、对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。 

      ③、传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。

      ④、一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改

      ⑤、对于任何非空引用值 x,x.equals(null) 都应返回 false。

     == 比较两个对象在栈内存中的引用地址是否相等。

    getClass()在 Object 类中如下,作用是返回对象的运行时类。

    public final native Class<?> getClass();

    这里详细的介绍 getClass 方法返回的是一个对象的运行时类对象,这该怎么理解呢?Java中还有一种这样的用法,通过 类名.class 获取这个类的类对象 ,这两种用法有什么区别呢?

    public class Parent {}
    
    
    public class Son extends Parent{}
    
    
    public void testClass(){
        Parent p = new Son();
        System.out.println(p.getClass());
        System.out.println(Parent.class);
    }
    
    

    结论:class 是一个类的属性,能获取该类编译时的类对象,而 getClass() 是一个类的方法,它是获取该类运行时的类对象。

      还有一个需要大家注意的是,虽然Object类中getClass() 方法声明是:public final native Class<?> getClass();返回的是一个 Class<?>,但是如下是能通过编译的:

    Class<? extends String> c = "".getClass();

    也就是说类型为T的变量getClass方法的返回值类型其实是Class<? extends T>而非getClass方法声明中的Class<?>。

    hashCode 在 Object 类中定义如下:

    public native int hashCode();

    通过 equals 方法判断集合中的每一个元素是否重复,但是如果集合中有10000个元素了,但我们新加入一个元素时,那就需要进行10000次equals方法的调用,这显然效率很低。于是,Java 的集合设计者就采用了 哈希表 来实现。

    哈希算法也称为散列算法,是将数据依特定算法产生的结果直接指定到一个地址上。这个结果就是由 hashCode 方法产生。这样一来,当集合要添加新的元素时,先调用这个元素的 hashCode 方法,就一下子能定位到它应该放置的物理位置上。

    两个对象相等,其 hashCode 一定相同;

    两个对象不相等,其 hashCode 有可能相同;

    hashCode 相同的两个对象,不一定相等;

    hashCode 不相同的两个对象,一定不相等;

    对于 Map 集合,我们可以选取Java中的基本类型,还有引用类型 String 作为 key,因为它们都按照规范重写了 equals 方法和 hashCode 方法。但是如果你用自定义对象作为 key,那么一定要覆写 equals 方法和 hashCode 方法,不然会有意想不到的错误产生。

    toString 方法

      public String toString() {
            return getClass().getName() + "@" + Integer.toHexString(hashCode());
        }

    getClass().getName()是返回对象的全类名(包含包名),Integer.toHexString(hashCode()) 是以16进制无符号整数形式返回此哈希码的字符串表示形式。

    打印某个对象时,默认是调用 toString 方法,比如 System.out.println(person),等价于 System.out.println(person.toString())

    java.lang.Integer、

    public final class Integer extends Number implements Comparable<Integer>{}

    Integer 是用 final 声明的常量类,不能被任何类所继承。并且 Integer 类继承了 Number 类和实现了 Comparable 接口。 Number 类是一个抽象类,8中基本数据类型的包装类除了Character 和 Boolean 没有继承该类外,剩下的都继承了 Number 类,该类的方法用于各种数据类型的转换。Comparable 接口就一个  compareTo 方法,用于元素之间的大小比较

     int 类型在 Java 中是占据 4 个字节,所以其可以表示大小的范围是 -2 31——2 31 -1即 -2147483648——2147483647,我们在用 int 表示数值时一定不要超出这个范围了。

    返回基本类型的数据

    
    
    <select id="getByParentId" parameterType="java.lang.Integer" resultType="java.lang.Integer">
            SELECT COUNT(*) FROM se_install_check_item WHERE installCheckId = #{deviceInstallCheckId}
    </select>
    
    Integer getByParentId(@RequestParam(value = "deviceInstallCheckId", required = false)Integer deviceInstallCheckId);
    
    Map<String, Object> getByParentId(Integer ovId);
    
    	@Override
    	public Map<String, Object> getByParentId(Integer ovId) {
    		// TODO Auto-generated method stub
    		Map<String, Object> map = new HashMap<>();
    		Integer  counts = mapper.getByParentId(ovId);
    		map.put("counts", counts);
    		return map;
    	}
    
    
    	@PostMapping("/queryByParentId")
    	public AssembleJSON installcheckItem(String ids){
    		
    		if(null!=ids&&ids.length()>0) {
    			for(int i =0;i<ids.split(",").length;i++) {
    				Map<String, Object> counts = service.getByParentId(Integer.parseInt(ids.split(",")[i]));
    				Object object = counts.get("counts");
    			}
    		}
    		
    		return AssembleJSON.SUCCESS(ids);
    	}
    
    
    var ids = "";
    	$.each(rows, function (k, j) {
    		ids += (ids == "" ? j[$id] : "," + j[$id]);
    	});
    	var paras = { ids: ids };
     	$.post("security/installitcheck/queryByParentId", paras, function (data) {
            if (data.code == "0") {
       		$Core.UI.message.error("未录入设备,不可提交!");
        		}
        		});
    

    二、返回 JavaBean 类型

    
    $.fn.serializeObject = function()
    {
        var o = {};
        var a = this.serializeArray();
        $.each(a, function() {
            if (o[this.name]) {
                if (!o[this.name].push) {
                    o[this.name] = [o[this.name]];
                }
                o[this.name].push(this.value || '');
            } else {
                o[this.name] = this.value || '';
            }
        });
        return o;
    };
    
        $("#btnSave").click(function () {
        	debugger;
            if ($("#submit_form").form("validate")) {
                var url = "security/userContacts/add";
                if ($("#id").val() != "")
                    url = "security/userContacts/upd";
                var paras = {
                    list:[],
                    publicServicesUser:{}
                };
                paras.publicServicesUser = $("#submit_form").serializeObject();
    
                for (var i= 14;i <= index; i++) {
                    var obj = {
                        contactsType: "",
                        contactsName:"",
                        contactsPhone:""
                    };
                    var row = $("#submit_table tr:eq(" + i + ")");
                    obj.contId = row.find("#contId").val();
                    obj.contactsType = row.find("#contactsType").combo("getValue");
                    obj.contactsName = row.find("#contactsName").textbox("getValue");
                    obj.contactsPhone = row.find("#contactsPhone").textbox("getValue");
                    paras.list.push(obj)
                }
                $.ajax({
                    type: "POST",
                    url: url,
                    contentType: "application/json; charset=utf-8",
                    data: JSON.stringify(paras),
                    dataType: "json",
                    success: function (message) {
                        if (message.code == 0) {
                            $Core.UI.message.success(message.msg);
                        }else{
                            $Core.UI.message.warning(message.msg);
                        }
                    }
                });
            }
        });
    
    

    	@PostMapping("upd")
    	public AssembleJSON update(@RequestBody UserContacts userContacts) {
    		PublicServicesUser publicServicesUser = userContacts.getPublicServicesUser();
    
    		SessionData sessionData =getCurrUserData();
    
    		publicServicesUser.setUpdateUser(sessionData.getUserId());
    		publicServicesUser.setUpdateUserName(sessionData.getUserName());
    		if (publicServicesUser.getUpdateTime() == null) {
    			publicServicesUser.setUpdateTime(new Date());
    		}
    
    		Map<Object, Object> objectObjectMap = MapUtil.object2Map(publicServicesUser);
    		String str = JSONArray.toJSONString(userContacts.getList());
    		PublicServicesUserTemp publicServicesUserTemp = MapUtil.map2Object(objectObjectMap, PublicServicesUserTemp.class);
    		publicServicesUserTemp.setContactsStr(str);
    		publicServicesUserTemp.setApproveState("0");
    		publicServicesUserTemp.setUpdateTime(new Date());
    		publicServicesUserTempService.insert(publicServicesUserTemp);
    
    		PublicServicesUser publicServicesUser1 = new PublicServicesUser();
    		publicServicesUser1.setId(publicServicesUser.getId());
    		publicServicesUser1.setApproveState("0");
    		publicServicesUser1.setUpdateTime(new Date());
    		userContacts.setPublicServicesUser(publicServicesUser1);
    		userContacts.setList(null);
    		return AssembleJSON.SUCCESS(userContactsService.updateContactsSelective(userContacts));
    	}
    

    --------------------------------------------------------------

        //保存按钮
        $("#btnAdd").click(function () {
        	debugger;
            if ($submit_form.form("validate")) {
                var url = add_url;
                if (key){
                    url = upd_url;
                }
                var paras = $submit_form.serializeArray();
                var rows2 = $("#dglist2").gridsub("getRows");
                var p2 = [];
                var flag=true;
                $.each(rows2, function (k, j) {
                    var temp2 = { deviceId: j.deviceId, deviceName: j.deviceName, deviceNumber: j.deviceNumber, assetsNumber: j.assetsNumber, deviceSpec: j.deviceSpec, propertyAscription: j.propertyAscription, company: j.company, number: j.number, installMoney: j.installMoney, total: j.total, installCheckId: key,unitMoney:j.unitMoney};
                    if(j.company==null){
                    	$Core.UI.message.error("请输入单位");
                    	flag=false;
                    	return;
                    }else if(j.number==null){
                    	$Core.UI.message.error("请输入数量");
                    	flag=false;
                    	return;
                    }else if(j.installMoney==null){
                    	$Core.UI.message.error("请输入安装费用");
                    	flag=false;
                    	return;
                    }else if(j.total==null){
                    	$Core.UI.message.error("请输入合计");
                    	flag=false;
                    	return;
                    }
                    
                    p2.push(temp2);
                });
                paras.push({ 'name': "acceptdetails", value: JSON.stringify(p2) });
                if(flag){
                	//已填写物资数量
                    $("#btnSave").attr("disabled", "disabled");
                    //    console.log(paras); return false;
    
                    $.post(url, paras, function (data) {
                        $("#btnAdd").removeAttr("disabled");
                        if (data.code == 0) {
                        	$Core.UI.message.success("操作成功!");
                        	 $('#btnAdd').attr('disabled',"true");
     
                        }
                        else {
                            $Core.UI.message.error(data.msg);
                        }
                    });
                }
    
            }
        });

    	@PostMapping("/update")
    	public AssembleJSON update(InstallCheck entity,String acceptdetails) {
    		SessionData sessionData =getCurrUserData();
    		entity.setUpdateUser(sessionData.getUserId());
    		entity.setUpdateUserName(sessionData.getUserName());
    		entity.setUpdateTime(new Date());
    		InstallCheck installCheck=service.update(entity,acceptdetails);
    		return AssembleJSON.SUCCESS(installCheck);
    		
    	}
    	@Override
    	public InstallCheck update(InstallCheck entity, String acceptdetails) {
    		mapper.updateByPrimaryKeySelective(entity);
    		
    		/*先删除字表*/
    		InstallCheckItem installCheckItem = new InstallCheckItem();
    		installCheckItem.setInstallCheckId(entity.getDeviceInstallCheckId());
    		List<InstallCheckItem> list = installCheckItemMapper.select(installCheckItem);
    		for(InstallCheckItem item : list) {
    			installCheckItemMapper.delete(item);
    		}
    		/*再新增*/
    		if(StringUtil.isNotEmpty(acceptdetails)) {
    			List<InstallCheckItem> InstallCheckItemdetails = JSONArray.parseArray(acceptdetails, InstallCheckItem.class);
    			for (InstallCheckItem detail : InstallCheckItemdetails) {
    				detail.setInstallCheckId(entity.getDeviceInstallCheckId());
    				installCheckItemMapper.insert(detail);
    			}	
    		}
    		return entity;
    	}
    

     

    三、返回Map类型

     $.get(form_url + key, function (data) {
    		    	debugger;
    		        $submit_form.form("load", data.data.installCheckVo	);
    		        $("#dglist2").datagrid({ data: data.data.deviceUserList });
    });

    Map<String, Object> getUid(Integer ovId);
    
    	@Override
    	public Map<String, Object> getUid(Integer ovId) {
    		// TODO Auto-generated method stub
    		Map<String, Object> map = new HashMap<>();
    		InstallCheckVo  installCheckVo = mapper.selectByPrimaryKey(ovId);
    		map.put("installCheckVo", installCheckVo);
    		
    		//明细
    		InstallCheckItem installCheckItem = new InstallCheckItem();
    		installCheckItem.setInstallCheckId(ovId);
    		List<InstallCheckItem> removeApplyItems = installCheckItemMapper.select(installCheckItem);
    		map.put("deviceUserList", removeApplyItems);
    		return map;
    	}
    
    
    
    
    
    

     

    根据id判断有无数据

    	@PostMapping("/queryByParentId")
    	public AssembleJSON installcheckItem(String ids){
    		
    		if(null!=ids&&ids.length()>0) {
    			for(int i =0;i<ids.split(",").length;i++) {
    				Map<String, Object> counts = service.getByParentId(Integer.parseInt(ids.split(",")[i]));
    				Object object = counts.get("counts");
    				if(""!=object&&"0".equals(object.toString())) {
    					return AssembleJSON.FAILURE;
    				}
    			}
    		}
    		
    		return AssembleJSON.SUCCESS(ids);
    	}
    Integer getByParentId(@RequestParam(value = "deviceInstallCheckId", required = false)Integer deviceInstallCheckId);
    	@Override
    	public Map<String, Object> getByParentId(Integer ovId) {
    		// TODO Auto-generated method stub
    		Map<String, Object> map = new HashMap<>();
    		Integer  counts = mapper.getByParentId(ovId);
    		map.put("counts", counts);
    		return map;
    	}
    <select id="getByParentId" parameterType="java.lang.Integer" resultType="java.lang.Integer">
            SELECT COUNT(*) FROM se_install_check_item WHERE installCheckId = #{deviceInstallCheckId}
    </select>
                	$.post("security/installitcheck/queryByParentId", param, function (data) {
                		
            			if (data.code != "0") {
            				$Core.UI.message.error("未录入设备,不可提交!");
            				return false;
            			}
                      }

    四、返回List类型

    public class UserVo {
         
        private List<User> userList;
        public List<User> getUserList() {
            return userList;
        }
        public void setUserList(List<User> userList) {
            this.userList = userList;
        }
    }
    
    @RequestMapping("selectAllUserAndList")
        public ModelAndView selectAllUserAndList(){
            List<User> listUser = userService.selectAllUser();
            ModelAndView mv = new ModelAndView();
            mv.addObject("listUser", listUser);
            mv.setViewName("list.jsp");
            return mv;
        }
    }

    由于我们在 JSP 页面 input 输入框定义的name属性名是 userList[${status.index}].id 这种形式的,这里我们直接用 UserVo 就能获取页面批量提交的 User信息

    public class UserContacts{
    
    	private PublicServicesUser publicServicesUser;
    
    	private List<Contacts> list;
    }
    
    	@Override
    	public UserContacts getById(Integer id) {
    		UserContacts userContacts = new UserContacts();
    		PublicServicesUser publicServicesUser = publicServicesUserMapper.selectByPrimaryKey(id);
    		Contacts contacts = new Contacts();
    		contacts.setUserId(id);
    		List<Contacts> list = contactsMapper.select(contacts);
    		List<Contacts> contactsList = new ArrayList<>();
    		for (Contacts contacts1 : list) {
    			if (contacts1.getContactsType() != 11 && contacts1.getContactsType() != 12 && contacts1.getContactsType() != 13 && contacts1.getContactsType() != 14) {
    				contactsList.add(contacts1);
    			}
    		}
    		userContacts.setPublicServicesUser(publicServicesUser);
    		userContacts.setList(contactsList);
    		return userContacts;
    	}
    
    	@GetMapping("/getById/{id}")
    	public AssembleJSON getById(@PathVariable Integer id) {
    		UserContacts userContacts = userContactsService.getById(id);
    		return AssembleJSON.SUCCESS(userContacts);
    	}
    
    
    
     $.get("security/PublicServicesUserTemp/getById/" + key, function (data) {
                	debugger;
                    _this.getMainData = data;
                    $("#submit_form").form("load", data.data.publicServicesUser);
                    var list = data.data.list;
     });

    有时候我们列表根据大标题和小标题及小标题下的文章的层级进行遍历。一次性获取的数据不能满足只遍历子标题下的文章会同时把大标题也遍历。这时候就需要我们用到集合数据类型,只遍历参数类型为集合的实体对象。

    定义实体对象

    
    	private static final long serialVersionUID = -8715592713023165170L;
    	
    	private String subjectClassifyId;
    	private String classifyName;
    	private String classifyIsShow;
    	private String classifyIsShowName;
    	private String classifyOrder;
    	private String subjectId;
    	
    	List<CheckAdvice> adviceList;

    后台封装集合参数

            @RequestMapping(value = "/gateway/subDetail")
    	public String getSubDataDetailById(Model model, HttpServletRequest request, String dataId) {
    		List<SubjectMgtClassify> dataList = gateWayService.getSubjectClassfiyAndAdviceList(dataId);
    		model.addAttribute("PROFILELIST",dataList);
    		return pathffix + "/subArticle";   
    	}
    
    
    	public List<SubjectMgtClassify> getSubjectClassfiyAndAdviceList(String dataId)
    	{
    		List<SubjectMgtClassify> subjectMgtClassify = getSubjectMgtClassify(dataId);
    		if(CollectionUtils.isEmpty(subjectMgtClassify))
    		{
    			return null;
    		}
    		for(SubjectMgtClassify subClass:subjectMgtClassify) {
    			List<CheckAdvice> adviceList = getAdviceByClassify(subClass.getSubjectClassifyId());
    			subClass.setAdviceList(adviceList);
    		}
    		return  subjectMgtClassify;
    	}
    
    
      	<select id="getAdviceByClassify" parameterType="com.model.SubjectMgtClassify" resultType="com.model.CheckAdvice">
    	select b.CHECK_ADVICE_ID as adviceId,b.TITLE as title,cta.attachment_id from (select a.*
    	from PB_CHECK_ADVICE a
    	where a.CHECK_ADVICE_ID in
    	(select t.advice_id
    	from pb_classify_rel_advice t
    	where t.subject_classify_id = #{subjectClassifyId,jdbcType=VARCHAR}) and
    	a.is_work=1) b
    	left join ct_attachment cta on cta.entity_id = b.CHECK_ADVICE_ID and
    	cta.is_delete=0 
      	</select>

    前台分开遍历只遍历子标题下的文章

    <div class="wrap main">
      <div class="wraper zt">
         
         <div class="cont">
           <div class="tit">
             <ul>
               <li>导读</li>
             </ul>
           </div>
           <div class="zt_con">
             <p style="text-indext:2em;">
               <#if HEADLIST??>
               ${HEADLIST.subjectBrief!''}
                </#if>
                <#if HEADLIST.attachmentId?exists>
     				<input id="headPicture" src="${rc.contextPath}/attach/download?attachmentId=${HEADLIST.attachmentId!''}" type="hidden">
    		    </#if>
             </p>
           </div>
           <#if PROFILELIST??&& PROFILELIST?size gt 0>
    		   <#list PROFILELIST as profile>
    		    <div class="tit">
             	<ul>
               <li> ${profile.classifyName!''}</li>
             	</ul>
               </div>
    		   <#if profile.adviceList?? &&  ( profile.adviceList?size>0)>
    		    <ul class="listTW">
    		    <#list profile.adviceList as profileItem>
    		    <li>
    			   	 
    		         <#if profileItem.useLink?? && profileItem.useLink == '1' && profileItem.oriLink??>
    		         	<h2><a href="${profileItem.oriLink!''}" target="_blank">${profileItem.title!''}</a></h2>
    		         <#else>
    		            <h2><a href="${rc.contextPath}/detail?dataId=${profileItem.adviceId!''}" target="_blank">${profileItem.title!''}</a></h2>
    		         </#if>
    		         <#if profileItem.attachmentId?exists>
     				    <img src="${rc.contextPath}/attach/download?attachmentId=${profileItem.attachmentId!''}">
    		         </#if>
    	          </li>
    		    </#list>
    		    </ul>
    		   </#if>
    		   </#list>
           </#if>
           
           
    
         </div>
      </div>
    </div>

    四、返回List<Map<String,Object>>类型

    public class AssembleJSON implements Serializable {
    	private static final long serialVersionUID = 1L;
    	
    	private Integer code;
    	private String msg;
    	private Object data;
    	/**
    	 * 获得返回数据
    	 * @return 数据
    	 */
    	public Object getData(){
    		return this.data;
    	}
    
    	public AssembleJSON() {
    	}
    
    	public AssembleJSON(Integer _status, String _message, Object _data){
    		this.code = _status;
    		this.msg = _message;
    		this.data = _data;
    	}
    	
    	/**
    	 * 返回成功(默认)
    	 */
    	public static AssembleJSON SUCCESS = new AssembleJSON(Constant.SUCCESS, Constant.REQUEST_SUCCESS_STRING, null);
    	public static AssembleJSON FAILURE = new AssembleJSON(Constant.FAILED, Constant.REQUEST_FAILED_STRING, null);
    	
    	/**
    	 * 返回成功JSON 状态码默认是 0
    	 * @param _data 数据
    	 * @return JSON对象
    	 */
    	public static AssembleJSON SUCCESS(Object _data){
    		return new AssembleJSON(Constant.SUCCESS, Constant.REQUEST_SUCCESS_STRING, _data);
    	}
    	
    	/**
    	 * 返回失败JSON 状态码默认是 -1000
    	 * @param _message 失败信息
    	 * @return JSON对象
    	 */
    	public static AssembleJSON FAILURE(String _message){
    		return new AssembleJSON(Constant.FAILED, _message, null);
    	}
    	
    	/**
    	 * 返回失败JSON,传入data 状态码默认是 0
    	 * @param _message 失败信息
    	 * @return JSON对象
    	 */
    	public static AssembleJSON FAILURE(String _message,Object _data){
    		return new AssembleJSON(Constant.FAILED, _message, _data);
    	}
    	/**
    	 * 返回成功JSON,
    	 * @param _code 返回的状态码
    	 * @param _message 返回的自定义信息
    	 * @param _data 返回的数据
    	 * */
    	public static AssembleJSON SUCCESS(Integer _code, String _message,Object _data){
    		return new AssembleJSON(_code, _message, _data);
    	}
    	/**
    	 * 返回成功JSON,
    	 * @param _code 返回的状态码
    	 * @param _message 返回的自定义信息
    	 * */
    	public static AssembleJSON SUCCESS(Integer _code, String _message){
    		return new AssembleJSON(_code, _message, null);
    	}
    }
    
    	@Override
    	public Map<String, Object> getUid(Integer installAcceptId) {
    		AssembleJSON json = dicService.getDicItem("installAcceptProject");
    		List<Map<String,Object>> items = (List<Map<String, Object>>) json.getData();
    		map.put("items", items);
    		
    		return map;
    	}
    
    

     

     

     $.get("security/installcompletevo/getUid/" + key, function (data) {
    $.each(data.data.items, function (k, j) {	 
                     		var addNum = ++a;
                    		var deviceName = j.deviceName;
                    		var checkIsRight = j.checkIsRight;
                    		var appendRow ="<tr> <td>"+addNum+
                    		"</td><td>"+j.itemName+
                    		"</td><td>"+
                    		"</td> <td>"+
                    		"</td> <td>"+
                    		"</td><td>"+
                    		"</td> <td>"+
                    		"</td><td>"+
                    		"</td> <td>"+j.checkIsRight+
                    		"</td></tr>"
                    		$("#dicItem").after(appendRow);
                    	});
    });

    返回 Hash<string,List<>>

    返回list里面包含list的,可以转换为多个list ,前台js进行拼装

    SELECT
      DISTINCT d.timestamp,d.`tag_value` AS tagValue,d.`device_code` AS deviceCode,CEIL(d.`tag_value`) AS tagCode
    FROM
      pr_scada_data d
    WHERE
      d.device_code = 'N64A749'
    AND d.tag_name LIKE '%出口压力'
    AND SUBSTRING(d.`timestamp`,1,11) = (SELECT SUBSTRING(m.`timestamp`,1,11) AS TIMESTAMP FROM (SELECT MAX(TIMESTAMP) AS TIMESTAMP FROM pr_scada_data WHERE device_code = 'N64A749') m)
    ORDER BY d.`timestamp` DESC

    	SELECT
    	DISTINCT d.timestamp,d.`tag_value` AS tagValue,d.`device_code` AS
    	deviceCode,CEIL(d.`tag_value`) AS tagCode
    	FROM
    	pr_scada_data d
    	WHERE
    	d.device_code = #{deviceCode,jdbcType=VARCHAR}
    	AND d.tag_name LIKE '%出口压力'
    	AND SUBSTRING(d.`timestamp`,1,11) = (SELECT SUBSTRING(m.`timestamp`,1,11)
    	AS TIMESTAMP FROM (SELECT MAX(TIMESTAMP) AS TIMESTAMP FROM
    	pr_scada_data WHERE device_code = #{deviceCode,jdbcType=VARCHAR}) m)
    	ORDER BY d.`timestamp`

    	public Map<String, List<String>> getRadarChartName(String deviceCode)
    	{
    		Map<String, List<String>> maps = new HashMap<String, List<String>>();
    		List<String> datalist = new ArrayList<String>();
    		List<String> textlist = new ArrayList<String>();
    		List<String> maxlist = new ArrayList<String>();
    		List<String> comparelist = new ArrayList<String>();
    		List<ScadaVO> radarCeilTime = prPreStationMapper.getRadarCeilTime(deviceCode);
    		List<ScadaVO> radarRecordChartName = prPreStationMapper.getRadarRecordChartName(deviceCode);
    		List<ScadaVO> newRadarRecordChartName = new ArrayList<ScadaVO>();
    		
    		ScadaVO firstScada = radarRecordChartName.get(0);
    		
    		for(ScadaVO scadaVO:radarCeilTime) {
    			comparelist.add(scadaVO.getTimestamp());
    		}
    		
    		newRadarRecordChartName.add(firstScada);
    		for(int i=radarRecordChartName.size()-1;i>0;i--) {
    			newRadarRecordChartName.add(radarRecordChartName.get(i));
    		}
    		
    		for(ScadaVO scadaVO:newRadarRecordChartName) {
    			datalist.add(scadaVO.getTagValue());
    			maxlist.add(scadaVO.getTagCode());
    			if(comparelist.contains(scadaVO.getTimestamp())) {
    				textlist.add(scadaVO.getTimestamp().substring(11, 13));
    			}else {
    				textlist.add("");
    			}
    			
    		}
    		
    		maps.put("datalist", datalist);
    		maps.put("textlist", textlist);
    		maps.put("maxlist", maxlist);
    		return maps;
    	}
    		var names = ["0:00","1:23","2:22","3:21","4:20","5:19","6:18","7:17","8:16","9:15","10:14","11:13","12:12","13:11","14:10","15:09","16:08","17:07","18:06","19:05","20:04","21:03","22:02","23:01"];
    		var url = CONTEXT_PATH +"/cusviews/dev/getRadarChartName";
    		var param = {
    				deviceCode:deviceCode
    		}
    		var retData = $.getData(url,param)
    		if(0==retData.datalist.length||0==retData.textlist.length||0==retData.maxlist.length){
    			return;
    		}
    		
    		var rlt = {
    				"data" : []
    			};
    		var rltdata = [];
    		
    		for(var i = 0, len = names.length; i < len; i++){
    			debugger;
    			var flag = false;
    			for(var key in retData.textlist){
    				var nItem = names[i], noKey = nItem.split(":")[0], nVal = nItem.split(":")[1];
    				if(nVal==retData.textlist[key]){
    					flag = true;
    					var obj = {};
    					obj.text = retData.textlist[key],obj.max = retData.maxlist[key];
    					rlt.data.push(obj);
    				}
    				if(!flag){
    					
    				}
    			}
    		}

     

    Map集合用于排序问题

    	public Map<String, Object>getRadarChartData(String deviceCode)
    	{
    		//每点的数据
    		List<ScadaVO> radarCeilTime = prPreStationMapper.getRadarCeilTime(deviceCode);
    		if(CollectionUtils.isEmpty(radarCeilTime))
    		{
    			return null;
    		}
    		String[] dateString = {"00","23","22","21","20","19","18","17","16","15","14","13","12","11","10","09","08","07","06","05","04","03","02","01"};
    		Map<String, Object> radarData = new HashMap<String,Object>(1);
    		for(int i=0;i<dateString.length;i++)
    		{
    			radarData.put(dateString[i], "0");
    		}
    		for(ScadaVO sc:radarCeilTime)
    		{
    			if(radarData.containsKey(sc.getTimestamp().substring(11, 13)))
    			{
    				radarData.put(sc.getTimestamp().substring(11, 13), sc.getTagValue());
    			}
    		}
    		return radarData;
    	}
    		var names = ["0:00","1:23","2:22","3:21","4:20","5:19","6:18","7:17","8:16","9:15","10:14","11:13","12:12","13:11","14:10","15:09","16:08","17:07","18:06","19:05","20:04","21:03","22:02","23:01"];
    		var url = CONTEXT_PATH +"/cusviews/dev/getRadarChartName";
    		var param = {
    				deviceCode:deviceCode
    		}
    		var retData = $.getData(url,param)
    		if(1000!=retData.status){
    			return;
    		}
    		var data = retData.data;
    		
    		var rlt = {
    				"data" : []
    			};
    		var rltdata = [];
    		var dataList = new Array();;
    		for(key in data){
    			var obj = {};
    			obj.text = key,obj.max = 3;
    			rlt.data.push(obj);
    			dataList.push(data[key]);
    		}

    通过map集合排序算法进行排序

    通过map进行数据的排序,list进行前台数据的封装

    	SELECT
    	DISTINCT d.timestamp,d.`tag_value` AS tagValue,d.`device_code` AS
    	deviceCode,CEIL(d.`tag_value`) AS tagCode
    	FROM
    	pr_scada_data d
    	WHERE
    	d.device_code = 'N53A295'
    	AND d.tag_name LIKE '%出口压力'
    	AND SUBSTRING(d.`timestamp`,1,11) = (SELECT SUBSTRING(m.`timestamp`,1,11)
    	AS TIMESTAMP FROM (SELECT MAX(TIMESTAMP) AS TIMESTAMP FROM
    	pr_scada_data WHERE device_code = 'N53A295') m)
    	ORDER BY d.`timestamp`

    	SELECT MAX(m.tagValue) AS tagValue FROM(SELECT MIN(b.`timestamp`) AS TIMESTAMP,CEIL(b.`tag_value`) AS tagValue
    	FROM
    	(SELECT
    	DISTINCT d.timestamp,d.`tag_value`,d.`device_code`
    	FROM
    	pr_scada_data d
    	WHERE
    	d.device_code = 'N53A295'
    	AND d.tag_name LIKE '%出口压力'
    	AND SUBSTRING(d.`timestamp`,1,11) = (SELECT SUBSTRING(m.`timestamp`,1,11)
    	AS TIMESTAMP FROM (SELECT MAX(TIMESTAMP) AS TIMESTAMP FROM
    	pr_scada_data WHERE device_code = 'N53A295') m)
    	ORDER BY d.`timestamp` DESC)
    	b GROUP BY SUBSTRING(b.`timestamp`,12,2)) m
    	public Map<String, List<String>> getRadarChartData(String deviceCode)
    	{
    		Map<String, List<String>> maps = new HashMap<String, List<String>>();
    		//统计整点的最大数据
    		String radarCeilTime = prPreStationMapper.getRadarCeilTime(deviceCode);
    		//数据库已有的真实的数据
    		List<ScadaVO> radarRecordChartName = prPreStationMapper.getRadarRecordChartName(deviceCode);
    		if(StringUtil.isEmpty(radarCeilTime))
    		{
    			return null;
    		}
    		if(CollectionUtils.isEmpty(radarRecordChartName)) {
    			return null;
    		}
    		String radarMaxTime = prPreStationMapper.getRadarMaxTiem(deviceCode);
    
    		String[] dateString = {radarMaxTime+"00:00",radarMaxTime+"23:00",radarMaxTime+"22:00",radarMaxTime+"21:00",radarMaxTime+"20:00",radarMaxTime+"19:00",radarMaxTime+"18:00",radarMaxTime+"17:00",radarMaxTime+"16:00",radarMaxTime+"15:00",radarMaxTime+"14:00",radarMaxTime+"13:00",radarMaxTime+"12:00",radarMaxTime+"11:00",radarMaxTime+"10:00",radarMaxTime+"09:00",radarMaxTime+"08:00",radarMaxTime+"07:00",radarMaxTime+"06:00",radarMaxTime+"05:00",radarMaxTime+"04:00",radarMaxTime+"03:00",radarMaxTime+"02:00",radarMaxTime+"01:00"};
    		
    		Map<String, Object> radarData = new HashMap<String,Object>(1);
    
    		//设置整点默认值,以最接近整点的数据最大的整数值设置,如果没有当前整点,则统一默认设置其他整点
    	
    			for(int i=0;i<dateString.length;i++)
    			{
    				radarData.put(dateString[i], radarCeilTime);
    			}
    
    		//查询真实数据
    		for(ScadaVO sc:radarRecordChartName)
    		{
    			radarData.put(sc.getTimestamp().substring(0, 16), sc.getTagValue());	
    		}
    		
    	      Map<String, Object> map = new TreeMap<String, Object>(
    	                new Comparator<String>() {
    	                    public int compare(String obj1, String obj2) {
    	                        // 降序排序
    	                        return obj2.compareTo(obj1);
    	                    }
    	                });
    	              for (Map.Entry<String, Object> entry : radarData.entrySet()) {
    	            	  map.put(entry.getKey(), entry.getValue());
                       }
    	              
    	      //map转为list,否则前台获取object 通过for in获取的数据是无序的
    	      List<String> listKey = new ArrayList<String>();
    	      List<String> listValue = new ArrayList<String>();
    	      List<String> dataKey = new ArrayList<String>();
    			
    		  //echarts顺时针展示
    	      String zeroTime = radarMaxTime+"00:00";
    	      Object firstMap = map.get(zeroTime);
    	      listKey.add(zeroTime);
    	      listValue.add(String.valueOf(firstMap));
    		  if(!radarCeilTime.equals(String.valueOf(map.get(zeroTime)))) {
    			  dataKey.add(String.valueOf(firstMap));
    	       }else {
    	    	   dataKey.add("");
    	       }
    	      Iterator<String> it = map.keySet().iterator();
    	       while (it.hasNext()) {
    	       String key = it.next().toString();
    	       if(!zeroTime.equals(key)) {
    		       listKey.add(key);
    		       listValue.add(radarCeilTime);
    		       if(!radarCeilTime.equals(String.valueOf(map.get(key)))) {
    		    	   dataKey.add(String.valueOf(map.get(key)));
    		       }else {
    		    	   dataKey.add("");
    		       }
    	        }
    	       }
    	       
    	       maps.put("textlist", listKey);
    	       maps.put("maxlist", listValue);
    	       maps.put("datalist", dataKey);
    		   return maps;
    	}

    	showRadarChart:function(deviceCode){
    		debugger;
    		var names = ["0:00","1:23","2:22","3:21","4:20","5:19","6:18","7:17","8:16","9:15","10:14","11:13","12:12","13:11","14:10","15:09","16:08","17:07","18:06","19:05","20:04","21:03","22:02","23:01"];
    		var url = CONTEXT_PATH +"/cusviews/dev/getRadarChartName";
    		var param = {
    				deviceCode:deviceCode
    		}
    		var retData = $.getData(url,param)
    		if(1000!=retData.status){
    			return;
    		}
    		var data = retData.data;
    		if(null==data){
    			return;
    		}
    		
    		var rlt = {
    				"data" : []
    			};
    		var rltdata = [];
    		var dataList = new Array();
    		
    		$.each(data.textlist,function(j,k){
    			let obj = {};
    			obj.text = k;
    			obj.max = data.maxlist[j];
    			rlt.data.push(obj);
    		})
    
    		var myChart = echarts.init(document.getElementById("radarChart"));
    		var option = {
    			    title : {
    			        text: '出口压力',
    			        subtext: ''
    			    },
    			    tooltip : {
    			       /* trigger: 'axis',*/
    			    	extraCssText:'width:120px;height:80px;',
    			    	textStyle:'8'
    			    },
    			    legend: {
    			        orient : 'vertical',
    			        x : 'right',
    			        y : 'bottom',
    			        data:[{
    			            name: '出口压力 ',
    			            // 强制设置图形为圆。
    			            icon: 'circle',
    			            // 设置文本为红色
    			            textStyle: {
    			                color: '#FF0000'
    			            }
    			        }]
    			    },
    			    toolbox: {
    			        show : true,
    			        feature : {
    			            mark : {show: true},
    			            dataView : {show: true, readOnly: false},
    			            restore : {show: true},
    			            saveAsImage : {show: true}
    			        }
    			    },
    			    polar : [
    			       {   
    	                   name:{
                               show: true, // 是否显示工艺等文字
                               formatter: null, // 工艺等文字的显示形式
                               textStyle: {
                                 color:'#a3a5b6' // 工艺等文字颜色
                               }
                            },
    			           indicator : rlt.data
    			        	   /*[
    			               { text: 0, max: 6000},
    			               { text: 5, max: 16000},
    			               { text: 4, max: 30000},
    			               { text: 3, max: 38000},
    			               { text: 2, max: 52000},
    			               { text: 1, max: 25000}
    			            ]*/
    			        }
    			    ],
    			    calculable : true,
    			    series : [
    			        {
    			            name: '出口压力 ',
    			            type: 'radar',
    			            data : [
    			                 {
    			                   /* value : [5000, 14000, 28000, 31000, 42000, 21000],*/
    			                	 value : data.datalist,
    			                    name : '出口压力',
    			                    itemStyle: {
    			                        normal: {
    			                            color: '#FF0000',
    			                            lineStyle: {
    			                                color: '#FF0000',
    			                            }
    			                        },
    			                    },
    			                }
    			            ]
    			        }
    			    ]
    			};
    		myChart.setOption(option);

    Map<String,List<ScadaVO>>

    	public Map<String,List<ScadaVO>> getScadaAirDataList()
    	{
    		List<ScadaVO> list = scadaMapper.getScadaAirDataList();
    		if(CollectionUtils.isEmpty(list))
    		{
    			return null;
    		}
    		Map<String,List<ScadaVO>> result = new HashMap<String,List<ScadaVO>>(1);
    		for(ScadaVO scada:list)
    		{
    			if(result.containsKey(scada.getTagCode()))
    			{
    				result.get(scada.getTagCode()).add(scada);
    			}
    			else
    			{
    				List<ScadaVO> dataList = new ArrayList<ScadaVO>(1);
    				dataList.add(scada);
    				result.put(scada.getTagCode(), dataList);
    			}
    			
    		}
    		return result;
    	}

    	var result = {};
    	var url = CONTEXT_PATH + '/cusviews/scada/getScadaAirData';
    	var retData = $.getData(url,null);
    	if(1000 != retData.status){
    		return;
    	}
    	var data = retData.data;
    	if(null == data || undefined == data){
    		return;
    	}
    	function getGasList(tagCode){
    		if($.isEmptyArray(data[tagCode])){
    			return;
    		}
    		var gasList = new Array();
    		for(var i=0;i<data[tagCode].length;i++){
    			debugger;
    			var obj = new Object();
    			obj.name = data[tagCode][i].timestamp;
    			var arr =  new Array();
    			arr.push(data[tagCode][i].timestamp)
    			arr.push(data[tagCode][i].tagValue);
    		    obj.value = arr;
    		    gasList.push(obj);
    		}
    		return gasList;
    	}
    	result["xijiaoList"] = getGasList("FRQA001A.PV");
    	result["ciquList"] = getGasList("FRQB003A.PV");

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • Java重复调用的代码块—方法

    千次阅读 2020-07-31 15:30:07
    在面向对象的程序设计中,方法是一个很重要的概念,体现了面向对象三大要素中“封装”的思想。“方法”又被称为“函数”,在其他的编程语言中都有这个概念,其重要性也是不言而喻的。 在本质上,一个类描述了两件...

    对 java 知识点重新进行了总结与更新,点我查看

    在面向对象的程序设计中,方法是一个很重要的概念,体现了面向对象三大要素中“封装”的思想。“方法”又被称为“函数”,在其他的编程语言中都有这个概念,其重要性也是不言而喻的。

    在本质上,一个类描述了两件事情。⑴ 一个对象知道什么(what’s an object knows)? ⑵ 一个对象能做什么(what’san object does)?第1件事情对应于对象的属性(或状态)。第2件事情对应于对象的行为(或方法)

    Person类(Person.java)
    在这里插入图片描述
    针对范例的Person类,有如下的示意图。
    在这里插入图片描述
    注意,这里的Person类仅是为了说明问题,本例的程序并不能单独运行。在Person类中,有实例变量name和age,它们描述了该类定义的对象所能感知的状态(或属性),关于类属性的使用,在前面的文章中我们已经详细的讨论了。而针对类的属性,如何操作这些属性,就是指该类定义的对象所能实施的行为,或者说,该对象所具备的方法,这里将重点讨论类中方法的使用规则。

    方法的基本定义

    在前面的文章范例中,我们经常需要用到某两个整数之间的随机数,有没有想过把这部分代码写成一个模块——将常用的功能封装在一起,不必再“复制和粘贴”这些代码,直接采用这个功能模块的名称,就可以达到相同的效果。其实使用Java中“方法”机制就可以解决这个问题的。

    方法(method)用来实现类的行为。一个方法通常是用来完成一项具体的功能(function),所以方法在C++中也称为成员函数(member function)。英文“function”的这两层含义(函数与功能)在这里都能得到体现。

    在Java语言中,每条指令的执行都是在某个特定方法的上下文中完成的。一般方法的运用原理大致如下图所示。可把方法看成完成一定功能的“黑盒”,方法的使用者(对象)只要将数据传递给方法体(要么通过方法中的参数传递,要么通过对象中的数据成员共享),就能得到结果,而无需关注方法的具体实现细节。当我们需要改变对象的属性(状态)值时,就让对象去调用对应的方法,方法通过对数据成员(实例变量)一系列的操作,然后就再将操作的结果返回。
    在这里插入图片描述
    在Java中,方法定义在类中,它和类的成员属性(数据成员)一起构建一个完整的类。构成方法有四大要素。返回值类型、方法名称、参数、方法体。这是一种标准,在大多数编程语言中都是通用的。

    所有方法均在类中定义和声明。一般情况下,定义一个方法的语法如下所示。
    在这里插入图片描述
    方法包含一个方法头(method header)和一个方法体。下图以一个max方法来说明一个方法的组成部分。

    方法头包括修饰符、返回值类型、方法名和参数列表等,下面一一给予解释。

    修饰符(modifier):定义了该方法的访问类型。这是可选的,它告诉编译器以什么调用该方法。

    返回值类型(return type):指定了方法返回的数据类型。它可以是任意有效的类型,包括构造类型(类就是一种构造类型)。如果方法没有返回值,则其返回类型必须是void。方法体中的返回值类型要与方法头中定义的返回值类型一致。
    在这里插入图片描述
    方法名(method name):方法名称的命名规则遵循Java标识符命名规范,但通常方法名以英文中的动词开头。这个名字可以是任意合法标识符。

    参数列表(parameter list):参数列表是由类型、标识符组成的序列,每对之间用逗号分开。参数实际上是方法被调用时接收传递过来的参数值的变量。如果方法没有参数,那么参数表为空的,但是圆括号不能省略。参数列表可将该方法需要的一些必要的数据传给该方法。方法名和参数列表共同构成方法签名,一起来标识方法的身份信息。

    方法体(body):方法体中存放的是封装在{}内部的逻辑语句,用以完成一定的功能。

    方法(或称函数)在任何一种编程语言中都很重要。它们的实现方式大同小异。方法是对逻辑代码的封装,使程序结构完整条理清晰,便于后期的维护和扩展。面向对象的编程语言将这一特点进一步放大,我们可以通过对方法加以权限修饰(如private、public、protected等)来控制方法能够在何处被调用。灵活的运用方法和权限修饰符对编码的逻辑控制非常有帮助。

    方法的使用

    下面我们继续深化上述范例的程序,通过下面的实例讲解方法的使用。在Person类中有3个方法,在主函数中分别通过对象调用了这3方法。范例 :方法的使用(PersonTest.java)。

    方法的使用(PersonTest.java)。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    第05~08行定义了talk()方法,用于输出Person对象的name和age属性。

    第09~12行定义了setName()方法,用于设置Person对象的name属性。

    第13~16行定义了setAge()方法,用于设置Person对象的age属性。

    从上面描述3个方法所用的动词“输出”、“设置”,就可以印证我们前面的论述,方法是操作对象属性(数据成员)的行为。这里的“操作”可以广义的分为两大类:读和写。读操作的主要目的是“获取”对象的属性值,这类方法可统称为Getter方法。写操作的主要目的是“设置”对象的属性值,这类方法可统称为Setter方法。因此,在Person类中,talk()方法属于Getter类方法,而setName()和setAge()方法属于Setter类方法。

    代码第24行,声明了一个Person类的对象p1。第25~27行,分别通过“点”操作符调用了对象p1的setName()、setAge()及talk()方法。

    事实上,由于类的属性成员name和age前并没有访问权限控制符(03~04行),由9.3.1小节讲解的知识可知,变量和方法前不加任何访问修饰符,属于默认访问控制模式。在这种模式下的方法和属性,在同一个包(package)内是可访问的。因此,在本例中,setName()、setAge()不是必需的,第25~26行的代码完全可以用下面的代码代替,而运行的结果是相同的。

    在这里插入图片描述
    这样看来,新的操作方法似乎更加便捷,但是上述的描述方式,违背了面向对象程序设计的一个重要原则——数据隐藏(data hiding),也就是封装性,这个概念我会在后期的文章中详细讲解。

    方法中的形参与实参

    如果有传递消息的需要,在定义一个方法时,参数列表中的参数个数至少为1个,有了这样的参数才有提供外部传递消息至本方法的可能。这些参数被称为形式参数,简称形参(parameter)。而在调用这个方法时,需要调用者提供与原方法定义相匹配的参数(类型、数量及顺序都一致),这些实际调用时提供的参数称为实际参数,简称实参(argument)。下图以一个方法max(int,int)为例说明了形参和实参的关系。
    在这里插入图片描述
    形参和是实参的关系如下。

    ⑴ 形参变量隶属于方法体,也就是说它们是方法的局部变量,只当在被调用时才被创建,才被临时性的分配内存,在调用结束后,立即释放所分配的内存单元。也就是说,当方法调用返回后,就不能再使用这些形式参数。

    ⑵ 在调用方法时,实参和形参在数量上、类型上、顺序上应严格保证一一对应的关系,否则就会出现参数类型不匹配的错误,从而导致调用方法失败。例如,假设t为包含max方法的一个对象,下面调用max方法时,提供的实参是不合法的。

    在这里插入图片描述

    方法的重载

    假设有这样的场景,需要设计一系列方法,它们的功能相似,都是输入某基本数据类型数据,返回对应的字符串,例如,若输入整数12,则返回长度为2的字符串“12”,若输入单精度浮点数12.34,则返回长度为5的字符串“12.34”,输入布尔类型值为false,则返回字符串“false”。由于基本数据类型有8个(byte、short、int、long、char、float、double及boolean),那么就需要设计8个有着类似功能的方法。因为这些方法的功能类似,如果它们都统一叫相同的名称,例如valueOf(),就对用户非常方便——方便取名、方便调用及方便记忆,但这样编译器就会“糊涂”了,因为它不知道该如何区别这些方法。就如一个班级里有8个人重名,都叫“张三”,授课老师无法仅从姓名上区别这8个同学,为了达到区分不同同学的目的,老师需要用到这些同学的其他信息(如脸部特征、声音特征等)。

    同样,编译器为了区分这些函数,除了用方法名这个特征外,还会用到方法的参数列表区分不同的方法。方法的名称及其参数列表(参数类型+参数个数)一起构成方法的签名(method signature)。就如同人们在正式文书上通过签名来区分不同人一样,编译器也可通过不同的方法签名来区分不同的方法。这种使用方法名相同但参数列表不同的方法签名机制,称之为方法的重载(method overload)。在调用的时候,编译器会根据参数的类型或个数不同来执行不同的方法体代码。下面的范例演示了String类下的重载方法valueOf使用情况。

    重载方法valueOf的使用演示(OverloadValueOf.java)
    在这里插入图片描述
    在这里插入图片描述
    代码第12~16行,分别使用了String类下静态重载方法valueOf()。这些方法虽然同名,都叫valueOf(),但它们的方法签名是不一样的,因为方法的签名不仅仅限于方法名称的区别,还包括方法参数列表的区别。第12行调用的valueOf()方法,它的形参类型是byte。第13行调用的valueOf()方法,它的形参类型是short…,第16行调用的valueOf()方法,它的形参类型是boolean。大家可以看到,使用了方法重载机制,在进行方法调用时就省了不少的麻烦事,对于相同名称的方法体,由编译器根据参数列表的不同,去区分调用哪一个方法体。

    重载方法println的使用(ShowPrintlnOverload.java)
    在这里插入图片描述
    在这里插入图片描述
    现在我们来详细解读一下“System.out.println()””的含义。System是在java.lang包中定义了一个内置类,在该类中定义了一个静态对象out,由于静态成员是属于类成员的,所以它的访问方式是“类名.成员名”——System.out。out本质上是PrintStream类的实例对象,println()则是PrintStream类中定义的方法。请大家回顾上面的一段程序,就会发现在前面章节中广泛使用的方法println()也是重载而来,因为第05~09行的“System.out.println()”可以输出不同的数据类型,相同的方法名+不同的参数列表是典型的方法重载特征。

    在大家自定义设计重载方法时,大家需要注意以下3点,这些重载方法之间。

    ⑴ 方法名称相同。

    ⑵ 方法的参数列表不同(参数个数、参数类型、参数顺序,至少有一项不同)。

    ⑶ 方法的返回值类型和修饰符不做要求,可以相同,也可以不同。

    下面以用户自定义的方法add()范例说明了方法重载的设计。

    加法方法的重载(MethodOverload.java)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    第04~07行,定义了方法add,其参数列表类型为“int , int ”,用于计算两个int类型数之和。第10~13行,定义了方法add,其参数列表类型为“float, float ”,用于计算两个float类型数之和。第16~19行,定义了一个同名方法add,其参数列表类型为“int, int, int”,用于计算int类型数之和。这三个同名的add方法,由于参数列表不同而构成方法重载。

    第25行,实例化一个本类对象。

    第28行,调用第一个add方法,计算两个整型数1和2之和。

    第32行,调用第二个同名add方法,计算两个浮点数float类型数1.2和2.3之和。

    第36行,调用第三个同名add方法,计算三个整型数1、2、3的和,并在下一行输出计算结果。

    Java方法重载是通过方法的参数列表的不同来加以区分实现的。虽然方法名称相同,它们都叫add,但是对于add( int a, int b )、add( float a, float b )及add( inta, int b, int c )这三个方法,由于它们的方法签名不同(方法签名包括函数名及参数列表),在本质上,对于编译器而言,它们是完全不同的方法,所以可被编译器无二义性地加以区分。本例仅仅给出了三个重载方法,事实上,add( int a, float b)、add( float a, int b )、add( double a, double b )等,它们和范例中的add方法彼此之间都是重载的方法。

    提示
    方法的签名仅包括方法名称和参数,因此方法重载不能根据方法的不同返回值来区分不同方法,因为返回值不属于方法签名的一部分。例如,int add(int, int)和void add(int, int)的方法签名是相同的,编译器会“认为”这两个方法完全相同而无法区分,故此它们无法达到重载的目的。

    方法重载是在Java中随处可见的特性,本例中演示的是该特性的常见用法。与之类似的还有“方法覆盖”,该特性是基于“继承”的

    构造方法

    构造”一词来自于英文 “Constructor”,中文常译为“构造器”,又称为构造函数(C++中)或构造方法(Java中)。构造方法与普通方法的差别在于,它是专用于在构造对象时初始对象成员的,其名称和其所属类名相同。下面将会详细介绍构造方法的创建和使用。

    构造方法

    在讲解构造方法的概念之前,首先来回顾一下对象声明并实例化的格式。
    在这里插入图片描述
    下面分别来观察这一步的4层作用。

    ⑴ 类名称:表示要定义变量的类型,只是有了类之后,变量的类型是由用户自己定义的;

    ⑵ 对象名称:表示变量的名称,变量的命名规范与方法相同,例如:studentName;

    ⑶ new:是作为开辟堆内存的唯一方法,表示实例化对象;

    ⑷ 类名称():这就是一个构造方法。

    所谓构造方法,就是在每一个类中定义的,并且是在使用关键字new实例化一个新对象的时候默认调用的方法。在Java程序里,构造方法所完成的主要工作是对新创建对象的数据成员赋初值。可将构造方法视为一种特殊的方法,其定义方式如下。
    在这里插入图片描述
    在使用构造方法的时候需注意以下几点。

    ⑴ 构造方法名称和其所属的类名必须保持一致。

    ⑵ 构造方法没有返回值,也不可以使用void。

    ⑶ 构造方法也可以向普通方法一样被重载。

    ⑷ 构造方法不能被static和final修饰。

    ⑸ 构造方法不能被继承,子类使用父类的构造方法需要使用super关键字

    构造方法除了没有返回值,且名称必须与类的名称相同之外,它的调用时机也与普通方法有所不同。普通方法是在需要时才调用,而构造方法则是在创建对象时就自动“隐式”执行。因此,构造方法无需在程序中直接调用,而是在对象产生时自动执行一次。通常用它来对对象的数据成员进行初始化。

    Java中构造方法的使用(TestConstruct.java)
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    第10~25行声明了一个Person类,为了简化起见,此类中只有Person的构造方法Person()和显示信息的方法show()。

    第12~17行声明了一个Person类的构造方法Person(),此方法含有一个对私有变量a赋初值的语句(14行)和两个输出语句(15~16行)。事实上,输出语句并不是构造方法必需的功能,这里它们主要是为了验证构造方法是否被调用了及初始化是否成功了。

    观察Person()这个方法可以发现,它的名称和类名称一致,且没有任何返回值(即使void也不被允许)。

    第05行实例化了一个Person类的对象p,此时会自动调用Person类的构造方法Person(),在屏幕上打印出:“构造方法被调用…”。

    第06行调用了Person中的show方法,输出指定信息。

    从此程序中大家不难发现,在类中声明的构造方法,会在实例化对象时自动调用且只被调用一次。

    大家可能会问,在之前的程序中用同样的方法来产生对象,但是在类中并没有声明任何构造方法,而程序不也一样可以正常运行吗?实际上,我们在执行javac编译java程序的时候,如果在程序中没有明确声明一个构造方法的话,编译器会自动为该类添加一个无参数的构造方法,类似于下表所示的代码。
    在这里插入图片描述
    这样一来,就可以保证每一个类中至少存在一个构造方法(也可以说没有构造方法的类是不存在的),所以在之前的程序之中虽然没有明确地声明构造方法,也是可以正常运行的。

    提示
    对于一个构造方法public Book() {}和一个普通方法public void Book() {},二者的区别在于,如果构造方法上写上void,那么其定义的形式就与普通方法一样了。构造方法是在一个对象实例化的时候只调用一次的方法,而普通方法则可通过一个实例化对象调用多次。正是因为构造方法的特殊性,它才有特殊的语法规范。

    构造方法的重载

    在Java里,普通方法是可以重载的,而构造方法在本质上也是方法的一种特例而已,因此它也可以重载。构造方法的名称是固定的—它们必须和类名保持一致,那么构造方法的重载,自然要体现参数列表的不同。也就是说,多个重载的构造方法彼此之间,参数个数、参数类型和参数顺序至少有一项是不同的。只要构造方法满足上述条件,便可定义多个名称相同的构造方法。这种做法在Java中是常见的,请看下面的程序。

    构造方法的重载(ConstructOverload.java)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    第1~21行声明了一个名为Person的类,类中有name与age两个私有属性和一个talk()方法,以及还有两个构造方法Person(),它们彼此的参数列表不同,因此所形成的方法签名也是不一致的,这2个方法名称都叫Person,故构成构造方法的重载。

    前者(06-10行)只有一个整型参数,只够用来初始化一个私有属性(age),故此用默认值“kehr”来初始化另外一个私有属性(name)(08行)。后者(12-16行)的构造方法中有两个形参,刚好够用来初始化类中的两个私有属性name和age属性。但为了避免构造方法中的形参name和age与类中的两个同名私有变量,在第14-15行中,用关键字this来表明,赋值运算符(=)的左侧变量是来自于本对象的成员变量(在03-04行定义),而“=”右侧的变量则是来自于构造方法的形参,它们是作用域仅限于构造方法的局部变量。构造方法中两个this引用表示“对象自己”。在本例中,可以删除“this.”不影响运行结果。关于this详细应用我会在后面的文章详细讲到。事实上,为了避免这种同名区分上的困扰,构造方法中的参数名称可以是任何合法的标识符。

    第26行,创建一个Person类对象p1并调用Person类中含有一个参数的构造方法:Person(int age),给age初始化,name的值采用默认值。

    第27行,再次创建一个Person类对象p2,调用Person类中含有两个参数的构造方法:Person (String name, int age),给name和age初始化。

    第28、29行调用Person类中的talk()方法打印信息。

    从本程序可以发现,构造方法的基本功能就是对类中的属性初始化,在程序产生类的实例对象时,将需要的参数由构造方法传入,之后再由构造方法为其内部的属性进行初始化,这是在一般开发中经常使用的技巧。但是有一个问题是需要大家注意,就是无参构造方法的使用,请看下面的程序。

    使用无参构造方法时产生的错误(ConstructWithNoPara.java)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    可以发现,在编译程序第05行时发生了错误,这个错误说找不到Person类的无参数的构造方法。在前面曾经提过,如果程序中没有声明构造方法,程序就会自动声明一个无参数的构造方法,可是现在却发生了找不到无参数构造方法的问题,这是为什么?读者可以发现第15~19行和21~25行声明了两个有参的构造方法。在Java程序中一旦用户显式声明了构造方法,那么默认的“隐式的”构造方法就不会被编译器生成。而要解决这一问题,只需要简单地修改一下Person类就可以达到目的——即在Person类中明确地声明一个无参数的构造方法,如下例所示。

    正确使用无参构造方法(ConstructOverload.java)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    可以看见,在程序的第15-19行声明了一无参的构造方法,此时再编译程序的话,就可以正常编译而不会出现错误了。无参构造方法由于无法从外界获取赋值信息,就用默认值初始化了类中的数据成员name和age(17-18行)。第05行,定义了一个Person类对象p,p使用了无参的构造方法Person()来初始化对象中的成员,第06行输出的结果就是默认的name和age值。

    构造方法的私有化

    由上面的分析可知,一个方法可根据实际需要,将其设置为不同的访问权限——public(公有访问)、private(私有访问)或默认访问(即方法前没有修饰符)。同样,构造方法也有public与private之分。到目前为止,前面的范例所使用的构造方法均属于public,它可在程序的任何地方被调用,所以新创建的对象也都可以自动调用它。如果构造方法被设为private,那么其他类中就无法调用该构造方法。换句话说,在本类之外,就不能通过new关键字调用该构造方法创建该类的实例化对象。请观察下面的代码。

    构造方法的私有化(PrivateCallDemo.java)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在第04行中,由于PrivateDemo类的构造方法PrivateDemo()被声明为private(私有访问),则该构造方法在外类是不可访问的,或者说它在其他类中是不可见的。所以在第17行,试图使用PrivateDemo()方法来构造一个PrivateDemo类的对象是不可行的。所以才有上述的编译错误。

    大家可能会问,如果将构造函数私有化会导致一个类不能被外类使用,从而不能实例化构造新的对象,那为什么还要将构造方法私有化呢?私有化构造方法有什么用途?事实上,构造方法虽然被私有了,但并不一定是说此类不能产生实例化对象,只是产生这个实例化对象的位置有所变化,即只能在私有构造方法所属类中产生实例化对象。例如,在该类的static void main()方法中使用new来创建。请大家观察下面的范例代码。

    构造方法的私有使用范例(PrivateConstructor.java)
    在这里插入图片描述
    在这里插入图片描述
    从此程序可以看出,第04行将构造方法声明为private类型,则此构造方法只能在本类内被调用。同时可以看出,本程序中的main方法也在PrivateConstructor类的内部(9-12行)。在同一个类中的方法,均可以相互调用,不论它们是什么访问类型。

    第11行,使用new调用private访问类型的构造方法PrivateConstructor(),用来创建一个匿名对象。由此输出结果可以看出,在本类中可成功实施实例化对象。

    大家可能又会疑问,如果一个类中的构造方法被私有化了,就只能在本类中使用,这岂不是大大限制了该类的使用?私有化构造方法有什么好处呢?请大家考虑下面的特定需求场景:如果要限制一个类对象产生,要求一个类只能创建一个实例化对象,该怎么办?

    我们知道,实例化对象需要调用构造方法,但如果将构造方法使用private藏起来,则外部肯定无法直接调用,那么实例化该类对象就只能有一种途径——在该类内部用new关键字创建该类的实例。通过这个方式,我们就可以确保一个类只能创建一个实例化对象。在软件工程中,这种设计模式被称之为单态设计模式(SingletonDesign Pattern)。

    许多时候整个系统只需要拥有一个全局对象,这样有利于协调系统整体的行为。例如,在某个Linux服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这样就大大简化了在复杂环境下的配置管理。在Windows中也有此设计的存在。例如,Windows中的回收站就是所有逻辑盘共享同一个回收站,这也是一个典型的单例模式设计。Java中的构造方法私有化就是为这种软件设计模式而服务的,请大家体会下面的范例。

    构造方法的私有使用范例2(TestSingleDemo.java)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    第06行声明一个Person类的对象p,但并未实例化,仅是在栈内存中为对象引用p分配了空间存储,p所指向的对象并不存在。

    第08行调用Person类中的getPerson()方法,由于该方法是公有的,可以借此方法返回Person类的实例化对象,并将返回对象的引用赋值给p。

    第17~20行将Person类的构造方法通过private关键字私有化,这样外部就无法通过其构造方法来产生实例化对象。

    第16行在类声明了一个Person类的实例化对象,此对象是在Person类的内部实例化,所以可以调用私有构造方法。此对象被标识为static类型,表示为一静态属性。另外,在声明Person对象的时候还加上了一个final关键字,此关键字表示对象PERSON不能被重新实例化。

    由于Person类构造方法是private,所以如 Person p = new Person () 已经不再可行了(06行)。只能通过“p = Person.getPerson();”来获得实例,而由于这个实例PERSON是static的,全局共享一个,所以无论在Person类的外部声明多少个对象,使用多少个“p = Person.getPerson();”,最终得到的实例都是同一个。也就是说,此类只能产生一个实例对象。这种做法就是上面提到的单态设计模式。所谓设计模式也就是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。

    在方法内部调用方法

    通过前面的几个范例,大家应该可以了解到,在一个Java程序中是可以通过对象去调用类中的各种方法。当然,类的内部也能互相调用彼此的方法,比如在下面的程序中,修改了以前的程序代码,新增加了一个public(公有的)say()方法,并用这个方法去调用私有的talk()方法。

    在类的内部调用方法(TestPerson.java)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    第09~12行,声明一个公有方法say(),此方法用于调用类内部的私有方法talk()。

    在第41行,调用Person类中的公有方法say(),本质上,通过say方法调用了Person类中的私有方法talk()。如果某些方法不方便公开,就可以用这种二次包装的模式来屏蔽不想公开的实现细节(如本例的talk()方法),这在某些应用背景下是有需求的。

    提示
    如果强调对象本身的话,第11行也可以写成“this.talk() ;”的形式。读者也许会觉得这样写有些多余,其实this的使用方法很多,在以后会完整地介绍。读者可自行修改上面的程序试验一下,看看结果是不是与原来的相同。

    方法的递归调用

    在讲解递归(Recursion)这个抽象之前,让我们来温馨回顾一下,在小时候当我们在缠着长辈讲故事,可能就被尊敬的长辈们用下面的故事来“忽悠”:

    从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?“从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?‘从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?……’”

    除了讲故事的人自己停下来不讲了,这个故事可以“无限的”讲下去,原因就是“故事”嵌套的“故事”就是“故事”本身,这就是语言上的“递归”的例子。

    在程序设计领域,递归是指函数(或方法)直接或间接调用自身的一种操作,如下图所示。递归调用能够大大减少代码量,将原本复杂的问题简化成一个简单的基础操作来完成。在编码过程中“递归调用”是一个非常实用的技巧。

    在这里插入图片描述
    从上图可以看出,函数(或方法)不论是直接调用自身,还是间接调用自身,都是一种无终止的过程。显然,在程序中不能出现这种无终止的递归调用。因此,在编写递归代码时,大家要特别注意,递归程序一定要有结束条件,这又被称作递归出口。如果一个递归函数缺少递归出口,执行时就会陷入死循环,其后果非常严重。递归出口可用if语句来控制,在满足某种条件时继续递归调用自身,否则就不再继续。

    下面我们通过一个例子来说明方法递归的调用原理。下面以计算“1+2+3+…+n”的值为例,分别实用非递归(nonrecursion)和递归(recursion)方式实现,读者可体会二者的区别。

    计算“1+2+3+…+n”,递归和非递归实现(RrecursionMethod. java)。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    第04~13行,定义一个非递归的函数addNonrecursion,采用for循环的方式来计算结果。

    第16~21行,定义一个递归函数addRecursion,采用递归的方式计算结果。

    第25行,实例化一个本类的对象test。

    第27行,调用非递归函数,计算1+2+3+…+10结果。第28行,输出计算结果。

    第30行,调用递归函数,计算1+2+3+…+10结果。第31行,输出计算结果。

    对于非递归的实现,用到了for循环语句,以1为基数不断循环叠加最终得出累加的结果。而在递归实现的操作中则明显不同。解决这个问题的思路是,若要计算addRecursion (n)=1+2+3+…+n结果,需要求{1+2+3+…+( n-1 )}+n;而计算1+2+3+…+( n-1 )的值,本质上就是计算addRecursion (n-1)的值。而计算addRecursion (n-1)的值,本质上就是{1+2+3+…+( n-2 )}+( n-1 ),而计算{1+2+3+…+( n-2 )}的值,本质上就是计算addRecursion (n-2),……,直到 addRecursion (1)=1,然 后 逐 级 返 回,addRecursion (2)=addRecursion (1)+2=1+2=3,addRecursion (3)= addRecursion(2)+3 = 3+ 3 =6,……,直 到 addRecursion (n)=addRecursion (n-1)+n,计算完毕。

    递归通过对方法本身的压栈和弹栈的方式,将每一层的结果逐级返回,通过逐步累加求得结果。下图给出addRecursion(5)的计算过程。
    在这里插入图片描述
    由上图可以得知,递归方式求解问题分为两个阶段:(1)第1个阶段是回推,逐级推导本级计算所需的条件。例如,如果需要计算addRecursion (n),需要计算addRecursion (n-1) +n,而计算addRecursion (n-1),需要计算addRecursion(n-2)+ (n-1)……一直回推到addRecursion (1)=1,这是递归的终止条件。然后开始第2个阶段。(2)第2个阶段是递推。addRecursion(2)=addRecursion(1)+2=3, addRecursion(3) = addRecursion(2)+3=6,……,直 到addRecursion(n) =addRecursion(n-1)+n,问题得以求解。

    提示
    虽然递归操作有许多的优点,但是缺点也很明显。使用递归实现需要函数压栈和弹栈的操作,所以程序的运行速度比不用递归实现要慢的多。如果操作不慎还极易出现死循环,读者编写代码过程中需要多加注意,一定要设置递归操作的终止条件。

    代码块

    代码块是一种常见的代码形式。它用大括号“{ }”将多行代码封装在一起,形成一个独立的代码区域,这就构成了代码块。代码块的格式如下。
    在这里插入图片描述
    代码块有四种。

    ⑴ 普通代码块。

    ⑵ 构造代码块。

    ⑶ 静态代码块。

    ⑷ 同步代码块。

    代码块不能够独立运行,须要依赖于其他配置。下面分别给予解释。

    普通代码块

    普通代码块是最常见的代码块。在方法名后(或方法体内)用一对“{ }”括起来的数据块就是普通代码块。它不能够单独存在于类中,需要紧跟在方法名后面,并通过方法调用。

    普通代码块演示(NormalCodeBlock.java)
    在这里插入图片描述
    在这里插入图片描述
    本例中,有两个普通代码块,第一个普通代码块是04~13行之间,是整个main方法的主体部分。第二个普通代码块是从06~09行之间,被左右花括号{}包括的部分。

    在普通代码块内,变量的作用范围自左花括号“{”内定义处开始,到右花括号“}”结束。因此,第二代码块中,在第07行定义变量x,其生命周期在第09行就结束了。而在第11行需重新定义整型变量x,这个变量x和第07行的变量x互不影响的,不过它们“碰巧”同名罢了。故第08行输出x =10,而第12行输出x = 100。

    但如果分别将第06行和第09行“看似无用途的”左右花括号“{}”删除掉,那么这个程序就无法编译通过,这是因为同一个main方法块内,同一个变量x被重复定义两次。第一次在第07行处定义,而第二次在第11行定义,重复定义编译无法通过,如下图所示。

    在这里插入图片描述

    构造代码块

    构造代码块就是在类中直接定义的,且没有任何前缀、后缀以及修饰符的代码块。通过前面的学习,我们应该知道,在一个类中,至少需要有一个构造方法(如果用户自己不显式定义,编译器会“隐式”地配备一个),它在生成对象时被调用。构造代码块和构造方法一样是在对象生成时被调用,但是它的调用时机比构造方法还要早。

    由于这种特性,构造代码块可用来初始化成员变量。如果一个类中有多个构造方法,这些构造方法都需要初始化成员变量,那么就可以把每个构造方法中相同的代码部分抽取出来,集中一起放在构造代码块中。这样利用构造代码块来初始化共有的成员变量,可大大减少不同构造方法中重复的代码,提高代码的复用性。

    构造代码块演示(ConsCodeBlock.java)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    我们首先分析类Person。第14~17行就是构造代码块,在第16行,对类的数据成员x进行初始化。如果语句“x = 100;”不放置于代码块中,要达到相同的效果,此句语句就需要分别出现在构造方法Person()和Person(String name)中。如果使用默认值来初始化类中的成员变量比较多,很明显,使用构造代码块能节省很多代码空间。第17行语句来显示构造代码块被调用了,实际的代码开发中,此类输出语句是不需要的。

    第19~24行,定义了一个无参的构造方法。第22行是对name数据成员变量赋初值“Guangzi”,然后调用了show()方法来输出name的值和x的值(x的初始值已经在构造代码块中初始化)。

    第26~31行,定义了一个有参构造方法。该构造方法有一个形式参数name,来接收外界输入,从而初始化类中的数据成员变量name,为了区分形参name和类中成员变量name,赋值运算符“=”左侧的变量,加上“this.”来表明其是来自类成员。

    思考一下,为什么构造方法Person()和Person(String name)中的name的初始化不放到构造代码块中呢?这是因为二者对name初始化的方式不同,前者是采用默认值的方法来初始化name,而后者是采纳外界输入的方法来初始化name。由此读者可以知道,构造代码块中的初始化是一个类的所有构造方法都共有的“交集”部分,具有个性化特征的初始化还是要放在各自的构造方法中。

    第33~37行,定义了show()方法,用来输出私有数据成员name和x。

    在分析完毕Person类,回到使用Person类的ConsCodeBlock类。在这个类中,仅有一个main方法。在main方法中,先定义了一个Person类对象p1(第05行),它调用Person类的无参构造方法来生成这个对象,从运行结果可以看出,构造代码块先执行,然后再执行构造方法,在构造代码块中,x的值被成功赋值为100。而在构造方法中,name被赋予默认值为“Guangzi”。

    然后,定义了一个Person类对象p2 (第07行),它调用Person类的有参构造方法来生成这个对象,从运行结果可以看出,依然是构造代码块先执行,然后再执行构造方法,在构造代码块中,x的值被赋值为100,而name的值被构造方法的外界输入(通过实参)初始化为“Zhang”。

    构造代码块不在任何方法之内,仅位于类的范围内,它的地位和其他方法体是对等的,可以理解为构造代码块是没有名称的方法体,但仅限用于对类数据成员的初始化,且仅运行一次。

    从上面的案例结果不难看出,在类被实例化的过程中,构造代码块内的代码比构造方法先执行。构造代码不仅可以减少代码量,也提高了代码的可读性,善用构造代码块能够给编码带来许多便利。

    静态代码块

    使用static关键字加以修饰并用大括号“{ }”括起来的代码块称为静态代码块,其主要用来初始化静态成员变量。它是最早执行的代码块。参见下面的范例。

    静态代码块演示(StaticCodeBlock.java)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    第04~07行,用static标识,用左右花括号“{}”括起来的区域就是一个静态代码块。

    第09~12行,是一个无参的构造方法,方法的名称与类同名,均为StaticCodeBlock。

    第14~16行,没有任何标识,用左右花括号“{}”括起来的区域就是前面讲到的构造代码块。

    在主方法(第18~27行)中,使用new关键字,分别创建了三个无名对象(分别在第22、24、26行)。据此来验证静态代码块执行多少次。

    从结果可以看出,静态代码块的执行时间主方法main()方法都要早。静态块还优先于构造方法的执行,而且不管有多少个实例化对象产生(本例中创建了3个对象),静态块都只执行一次。利用这种特性,静态代码块可以被用来初始化类中的静态成员变量。静态成员变量是属于所有类对象共享的,故此不会受到创建对象个数的影响。

    上面的案例可得出初步结论。在执行时机上,静态代码块在类加载时就会执行,因此早于构造代码块、构造方法。当静态代码块和main方法属于一个类时,静态代码块比main方法执行早。静态块的执行级别是最高的。

    方法与数组

    数组引用传递

    让数组b直接指向数组a(即b = a;),这样做的目的是为了提高程序运行的效率。试想一下,假如数组中有上万个元素,在拷贝数组时,如果将数组a的所有元素都一一拷贝至数组b,时间开销很大,有时候也不是必需的。所以,在Java语言中, b = a(a和b都是引用名)的含义就是将a起个别名"b"。之后,a和b其实就是指向的是同一个对象。在Java中,这种给变量取别名的机制称之为引用(reference)。

    抽象的概念都源于具体的表象。在现实生活中,“引用”的例子也很多,例如,周树人的“笔名”是鲁迅。一般来说,人们都会有一个正式的学名,同时也有个亲切的“乳名(小名)”。这些表象有不同的“名称”,在本质上,指向的事物都是同一个。我们说鲁迅先生写了很多脍炙人口的作品,实际上也是说周树人先生写了很多脍炙人口的作品。中国自主研制的CPU——龙芯,小名狗剩,外文名GodSon,它们三者名称虽然不同,但指向的都是中国自主研制的CPU。

    一个程序若想运行,必须驻入内存,而在内存中必然有其存储地址,通过这些内存地址,就可以找到我们想的数据。这些内存地址通常都很长(具体长度取决于JVM的类型),因为不容易记住,所以就给这些地址取个名称,这就是引用变量,这些引用变量存储在一块名叫“堆内存”的区域。

    那么所谓“引用”,就是Java对象在堆内存的地址赋给了多个“栈内存”的变量,由于Java禁止用户直接操作“堆整型、浮点型、布尔型等基本数据类内存”中对象的地址,所以只能用这些“栈内存”的多个引用名来间接操作它们对应的“堆内存”数据。所以,Java中的“引用”更类似于C/C++中的“指针”概念,所不同的是,C/C++中的“指针”可以被用户直接修改,而在Java中对内存的直接修改是被屏蔽的。

    在Java中,所有对象都是通过引用进行操作的。而数组也是一种对象。当将数组作为参数传递给方法时,传递的实际上就是该数组对象的引用。在方法中对数组的所有操作,都会映射到原数组中,这一点也是Java面向对象的一个重要特点。

    所谓的数组引用传递,就是将一块数组的堆内存空间交由多个栈内存所指向。这包括了方法通过参数列表接收数组和方法计算完毕后返回数组等两种情况,但不管数组操作形式如何改变,最终都属于引用传递。请注意,除了对象有这种特性外,整型、浮点型、布尔型等基本数据类型都不具备该特性。

    演示数组的引用传递 (ArrayReference.java)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    第3~9行,定义静态方法changeReferValue,分别对传入参数的值进行修改。

    第12~19行,定义静态方法printArr,用于将数组在终端打印出来。请读者注意第14行的for循环方式。在Java1.5以后的版本中,对于数组和集合框架(Collection)等类型的对象,提供了一种新的遍历方式,称为for-each
    在这里插入图片描述
    Java集合框架是由一套设计优良的接口和类组成的,可使程序员成批地操作数据或对象元素,第14~17行代码,完全可以替换为传统的for循环方式。
    在这里插入图片描述
    第21~26行,定义静态方法print,用于打印所有变量。

    第30~31行,在主方法中分别声明了整型、数组等2种不同类型的变量,并赋予初值。整型属于基本数据类型,而数组则属于引用数据类型。

    第34行,调用print方法,打印出没有调用changeReferValue方法前的各个变量值。

    第36行,调用changeReferValue函数,试图改变参数的值。

    第37行,再次调用print方法,用以查看调用changeReferValue方法之后,各个变量值的值是否得以改变。

    因为数组是对象,所以在changeReferValue方法的参数列表中,最后一个参数传递形式为“传引用”。换句话说,main方法中实参arr和changeReferValue方法中的形参myAr指向同一块内存空间。因此,在changeReferValue方法中,对形参arr所指向的数组数据的任何修改,都会同步影响到main方法中的实参arr所指向的数组数据(myAr和arr本质上就上一块内存区域)。在changeReferValue方法中,由于对于整型形参a和实参in之间是“传值”关系。在实参in将值赋值给形参a后,形参和实参二者之间再没有任何关联,所以在方法changeReferValue中对a的+1操作,并没有影响实参in的值,在本质上,形参a和实参in所指向的完全是不同的内存空间。

    在方法中实现排序

    在方法中对数组进行排序 (ArraySort.java)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    3~18行的sort方法是一个冒泡排序算法,对数组arr的元素从大到小排序。

    20~29行的printArr方法是将数组的元素输出。其中数组的输出用了for-each语法。

    34和36行,在main方法中分别输出排序前和排序后的数组元素。

    由于sort方法的参数传递方式数组对象的引用,故此,在sort方法体对数组arr的所有修改,都会在主函数定义的数组对象arr中体现出来,所以sort函数不需要返回值。在sort方法中,数组用使用“传引用”方式,保证了sort内的数组arr和main中的数组arr本质上同一个数组(它们的引用指向的是同一块内存空间),这样在sort方法中完成排序后,自然不用“多此一举”的将其排序的结果返回main方法了。

    让方法返回数组

    方法的返回值可以是Java所支持的任意一种类型。数组作为对象同样也可以成为方法的返回值。修改上述范例的排序函数,改变其返回值类型为整型数组(int[]),如下所示。

    演示方法返回数组 (ArrReturn.java)。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    04~20行的sort方法是一个冒泡排序算法,对数组arr的元素从大到小排序。与之前范例中的ArraySort不同的是,此处的sort方法返回的是一个整型数组int[]。

    21~30行的printArr方法是将数组的元素输出。使用的是for-each语法。

    第39行,调用sort()方法来实施对数组arr的排序。第37行和40行,在main方法中分别输出排序前和排序后的数组元素。

    在第36行中,声明一个整型数组arrnew,此时还是一个空引用。在第39行,arrnew用来接收sort方法返回的数组引用。结合上一个范例分析可知,在34行定义的数组arr和sort方法中的形参arr构成“引用”关系,即二者本质上指向的是同一块内存空间。而sort方法完成排序后,返回的arr数组对象的引用,又被arrnew所接收(39行),故此arrnew和arr指向的也是同一块内存空间,也就是它们有相同的“引用值”。所以,在sort方法中对arr的排序,也可以说对arrnew做了排序。

    以上的范例程序,不管如何的改变,实际上可以发现,数组对象引用的核心功能没有改变,还是一块堆内存设置了多个栈内存指向——一个数组对象(存储于堆内存中)对应多个别名(引用,存在于堆内存中)。

    与数组有关的操作方法

    Java对系统开发支持地非常好。一般来说,对于一些常用的功能,都会有相应的开发包支持,对于用户而言,比较麻烦的是要为这些开发包实施单独的配置,了解这些包提供的应用程序接口。Java对数组进行了封装和抽象,实现了Array接口。一些框架集合(collection)继承了接口,对数组的功能进行了扩展,形成了一类功能强大的工具集。在本书的第20章 Java类集框架,将会介绍这方面的知识点。在Java中,针对数组操作的支持主要有两个,数组的克隆和数组的排序。下面分别一一给予简介。

    数组的克隆

    由基本数据类型构建的数组,如:int []、 double[] 等,Java提供的内置方法并不是很多,最常用的方法是clone()方法,它会将数组复制一份,返回一个新的引用,常用来复制数组。数组对象提供的length属性用于记录数组的长度,即数组中包含元素的个数。

    数组有关的操作方法(ArrayMethod.java)
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    第03~10行所示的printArr方法,其功能是将数组的元素输出。

    在main方法中,第13行声明一个数组arr。在第14行,使用数组clone方法,新的数组arrnew克隆原数组的元素。第16行输出原来数组arr的元素。代码第20行,输出克隆数组arrnew的元素。

    代码第23~26行,判断数组对象arr和克隆数组arrnew的引用值是否相同。

    第14行声明一个新的数组arrnew,并将数组的值“克隆”(复制)给arrnew。从输出结果可以看出,此时数组arrnew和arr的元素是相同的,但是二者的引用值是不同的,也就是说,它们分别指向不同的堆内存地址。

    如果将13~14行改为如下代码:
    在这里插入图片描述
    那么,这时arrnew和arr指向的是相同的地址空间,即一个堆内存地址对应两个引用名。arrnew和arr是“别名”关系,它们的“外表”不一样,但指向的本质东西是相同的,这样,数组arrnew和数组arr本质上就是一个数组,它们包括的元素自然也是相同的。如果将arrnew指向的元素值实施操作, arr中的元素值自然也会相应发生改变。如下图所示。
    在这里插入图片描述
    而范例中使用数组的克隆,其机理和上面的数组名引用是不同的。通过“克隆”机制,数组arrnew在堆内存中另辟一块和数组arr等大的内存,然后一一复制arr中的元素,克隆完毕后,两个数组中的元素值是一一对应相同的。由于arr和newarr对应两块不同的内存空间,克隆之后,它们对各自元素的修改,不会对另外一个数组产生任何影响,如下图所示。
    在这里插入图片描述
    Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。这个方法将返回Object对象的一个拷贝。这里有两点需要特别说明。

    (1) “克隆”(拷贝)对象返回的是一个新对象,而不是一个已有对象的引用。

    ⑵ “克隆”(拷贝)对象与用 new操作符返回的新对象是有区别的,克隆对象是拷贝某个对象的当前信息,而不是对象的初始信息。打个比方说,如果把new构造出来的对象比喻成刚出生的“小羊羔”,过了一段时间,“小羊羔”长成一个“小山羊”了。这时,如果通过克隆操作,得到的是另外一个一模一样的“小山羊”,而不是当初new构造出来的“小羊羔”。

    数组的排序

    使用Java的包库对数组进行排序。
    在这里插入图片描述
    在这里插入图片描述
    代码03-12行,是一个输出数组元素的printArr方法。

    代码16行,定义了一个数组arr,并做了元素的初始化

    代码18行利用Java的包库提供的方法来排序,代码19行输出了排序后的数组元素。

    Java对数组的排序方法sort(),存在于util. Arrays的包中,我们要么使用本范例中的语法,明确的指定sort方法的来源。
    在这里插入图片描述
    要么在代码的第一行,导入java.util库包。
    在这里插入图片描述
    然后,再使用简洁的调用方式。
    在这里插入图片描述

    展开全文
  • Java集合面试题

    万次阅读 多人点赞 2019-06-25 14:46:19
    Java集合面试题 Java 集合框架的基础接口有哪些? Collection ,为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java 平台不提供这个接口任何直接的实现。 Set ,是一个不能包含重复元素的集合...
  • 史上最全面Java面试汇总(面试题+答案)

    万次阅读 多人点赞 2018-07-06 14:09:25
    JAVA面试精选【Java基础第一部分】 JAVA面试精选【Java基础第二部分】 JAVA面试精选【Java基础第三部分】 JAVA面试精选【Java算法与编程一】 JAVA面试精选【Java算法与编程二】 Java高级工程师—面试(1) ...
  • java面试题2019_java面试题及答案_java面试题库

    千次阅读 多人点赞 2019-05-16 09:31:30
    1、一个.java源文件中是否可以包括多个类(不是内部类)?有什么限制? 2、Java有没有goto? 3、&和&&的区别? 4、switch语句能否作用在byte上,能否作用在long上,能否作用在String上? 5、short s1 = ...
  • JAVA框架

    千次阅读 2019-10-15 10:42:43
    JAVA框架SpringSpringMVCSpringBootDubboMavenRedisMybatis Spring SpringMVC SpringBoot Dubbo Maven Redis Mybatis
  • Java正则表达式

    千次阅读 2019-07-06 19:03:50
    java.util.regex程序包只包含用于实现Java正则表达式处理技术的两个类,分别名为Pattern和Matcher。自然而然你会想到正则表达式由模式匹配(pattern matching)而成。java.lang还定义了一个新接口,它支持这些新的类...
  • Java基础面试题

    千次阅读 2019-06-03 16:59:59
    JDK:java development kit: Java开发工具包,包括了JRE,提供有一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar) JRE:Java runtime environment:java运行时环境,包括有Java的JVM,...
  • JAVA 基础

    千次阅读 多人点赞 2016-11-04 10:33:04
    本身是搞Android开发的,觉得java基础太重要了,所以就开始写一点java基础方面的内容,后续会继续的写一些关于基础方面的博文
  • java的继承和封装

    千次阅读 2020-07-05 19:11:51
    2.继承:减少代码重复率 3.多态:提高代码的灵活性和扩展性 二、规范化创建类的步骤 这里我用宠物这个类作为父类 以及狗作为子类对象 ps:子类不可以继承父类的三个资源 1.私有化的属性和方法 2.构造方法(可以...
  • Java中的封装,继承和多态(详解)

    千次阅读 多人点赞 2021-05-21 22:07:08
    所谓的封装就是类的属性和方法使用private修饰,不允许类的调用者直接访问,我们定义如下一个类,可以看到所有的成员变量和成员方法都使用private修饰了,我们现在来使用一下这个类。 当我们使用的时候编译器给出...
  • 小甲鱼零基础入门学习python笔记

    万次阅读 多人点赞 2019-08-14 11:06:30
    004 改进我们的小游戏 •第一个改进要求:猜错的时候程序提示用户当前的输入比答案大了还是小了 与操作and •第二个改进要求:程序应该提供多次机会给用户猜测,专业点来讲就是程序需要重复运行某些代码。...
  • 1. 返回值【难点】 1.1 从生活中找出什么是返回值 餐馆吃饭 烤羊排 138一份 --> 声明 付款 138RMB --> 给予实际参数 厨师做好烤羊排之后,让服务员给你端上来 --> 厨师完成操作,给予的返回值 ATM机取...
  • 方法在一些语言中被称为函数,是对于一些重复使用的具有复杂逻辑的代码封装,给被封装代码起一个名字,并且可以随时通过这个名字调用被封装代码。 为什么要定义方法? ​ 假如有三个学生的成绩,需要计算他们...
  • 小疯手把手带你整合SpringMVC+Spring+MyBatis三大框架,俗称SSM,用它完全代替传统的SSH框架,它们最优雅的一面发挥出来。整合配置结束后,会有一个应用实例“图书管理系统”带给大家,希望能快速上手这个框架!
  • Java基础教程(全代码解析)

    万次阅读 多人点赞 2018-02-10 21:01:26
    关注我,每天都有优质技术文章推送,工作,学习累了的时候放松一下自己。 本篇文章同步微信公众号 欢迎大家关注我的微信公众号:「醉翁猫咪」 ...接下来代码展示理解 public class Test{ char c = '...
  • 前端面试题

    万次阅读 多人点赞 2019-08-08 11:49:01
    typeof运算符返回值中有一个跟javascript数据类型不一致,它是________”function”_________。 68 定义了一个变量,但没有为该变量赋值,如果alert该变量,javascript弹出的对话框中显示___undefined______ 。 68...
  • js面试题

    千次阅读 多人点赞 2019-04-09 19:42:32
    Java、C 等语言中,作用域为 for 语句、if 语句或{}内的一块区域,称为作用域; 而在 JavaScript 中,作用域为 function(){}内的区域,称为函数作用域。 JavaScript 变量声明提升: 在 JavaScript 中,...
  • 测试开发笔记

    万次阅读 多人点赞 2019-11-14 17:11:58
    代码复杂度 版本管理 针对基础测试基础版本要进行充分的测试 验收前的最后一个版本一定要进行完全重复测试 测试方法 黑盒方法 功能问题 无法保证所有的代码逻辑都被执行到 用白盒测试思想补充黑盒测试 静态测试方法 ...
  • mybatis

    千次阅读 2019-06-28 13:27:05
    mybatis第一天 1.mybatis概述和环境搭建 mybatis概述 ...mybatis是一门java语言编写持久层框架,大大简化了jdbc操作,省去了我们注册驱动,获取连接等细节操作。 org.mybatis mybatis 3.4.5 ...
  • Java面向对象三大特性(封装、继承、多态)

    千次阅读 多人点赞 2021-05-19 20:59:41
    文章目录前言一、封装1.封装的概念2.private实现封装3.getter和setter方法4.封装的好处二、继承1.extends实现...在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者 封装的本质就是让类的调用者不必太多的.
  • SpringMVC

    千次阅读 多人点赞 2018-12-26 20:51:57
    需要在类上添加@Controller注解,Controller交由Spring管理 在方法上面添加@RequestMapping注解,里面指定请求的url。其中“.action”可以加也可以不加 model存储数据实际上就是对request.setAttribute的封装 ...
  • HashSet 是一个没有重复元素的集合。 它是由HashMap实现的(HashSet中大量调用了HashMap的方法,其内部封装了一个HashMap ),不保证元素的顺序,而且HashSet允许使用 null 元素
  • 使用java的API编写代码

    千次阅读 2019-07-28 13:53:49
    使用java的API编写代码 JavaBean 在Java中,有很多class的定义都符合这样的规范: 若干private实例字段; 通过public方法来读写实例字段。 public class Person { private String name; private int age; ...
  • 数据库面试题

    千次阅读 多人点赞 2018-05-24 10:46:20
    数据库面试题 1.什么是存储过程?用什么来调用? 存储过程是一个预编译的SQL语句,优点是允许模块化的设计,就是说只...2)可以供外部程序调用,比如:java程序。 2.存储过程的优缺点? 优点: 1)存储过程是...
  • C#基础教程-c#实例教程,适合初学者

    万次阅读 多人点赞 2016-08-22 11:13:24
    CLR为C#语言中间语言代码运行提供了一种运行时环境,C#语言的CLR和JAVA语言的虚拟机类似。这种执行方法使运行速度变慢,但带来其它一些好处,主要有:  通用语言规范(Common Language Specification,CLS):.NET...
  • SSM面试题

    千次阅读 多人点赞 2019-07-11 19:13:58
    IOC/DI(控制反转/依赖注入) :dao依赖注入到service层,service层反转给action层,Spring顶层容器为BeanFactory。 ②. AOP:面向切面编程 2、Spring的事务? 编程式事务管理:编程方式管理事务,极大灵活...
  • 提示:该文章是本人复习时总结,内容不全,请见谅。 Java面向对象知识点前言一、封装二、多态三、继承四、抽象五级标题六级标题总结 前言 面向对象(Object ...封装的含义:(自己理解)一个对象属性私有化,

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 64,790
精华内容 25,916
关键字:

java把重复的返回值代码封装

java 订阅