1.JDK、JRE、JVM

JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。

JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。包括Java虚拟机和Java程序所需的核心类库等。

JVM:Java虚拟机,Java程序运行在虚拟机上,不同平台有自己的处理机,因此Java可以跨平台。

需要运行 java 程序,只需安装 JRE 就可以了,编写 java 程序,需要安装 JDK。

2.JAVA数据类型

基本类型 位数 字节
int 32 4
short 16 2
long 64 8
byte 8 1
char 16 2
float 32 4
double 64 8
boolean false true

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

3.String 与new String

  1. String str ="lymn";
  2. String newStr =new String ("lymn");

String str =”lymn”

先在常量池中查找有没有lymn这个对象,如果有,就让str指向那个lymn。如果没有,在常量池中新建一个lymn对象,并让str指向在常量池中新建的对象lymn。

String newStr =new String (“lymn”)

new String(“lymn”)先在常量池中查找,若没有则创建“lymn”,而后通过new在堆内存中创建对象,把“lymn”拷贝赋值。

4.equals、hashCode与==

==

  • 如果是基本类型,==表示判断它们值是否相等;
  • 如果是引用对象,==表示判断两个对象指向的内存地址是否相同。

equals

equals方法是基类Object中的实例方法。在Object类中,equals方法是用来比较两个对象的引用是否相等,即是否指向同一个对象。

  1. public boolean equals(Object obj) {
  2. return (this == obj);
  3. }
  • 如果没有对equals方法进行重写,则比较引用类型变量所指向的对象地址;
  • 诸如String、Date等类对equals方法进行了重写的话,比较所指向对象的内容。

equals 重写原则

  • 对称性: 如果x.equals(y)返回true,那么y.equals(x)也应该返回是true;
  • 自反性: x.equals(x)必须返回是true ;
  • 类推性: 如果x.equals(y)返回是true,而且y.equals(z)返回是true,那么z.equals(x)也应该返回是true;
  • 一致性: 如果x.equals(y)返回是true,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是true ;

hashCode

Object中的 实例native方法会针对不同的对象返回不同的整数

  1. public native int hashCode();

5.hashCode()与equals()

  • 两个对象equals相等,则它们的hashcode必须相等;
  • 两个对象equals不相等,则它们的hashcode有可能相等,也有可能不等;
  • hashCode() 不相等,equals一定不相等;
  • 重写了euqls方法的对象必须同时重写hashCode()方法。

hashcode是系统用来快速检索对象,equals方法是用来判断引用的对象是否一致。

String重写

  1. public boolean equals(Object anObject) {
  2. if (this == anObject) {
  3. return true;
  4. }
  5. if (anObject instanceof String) {
  6. String anotherString = (String)anObject;
  7. int n = value.length;
  8. if (n == anotherString.value.length) {
  9. char v1[] = value;
  10. char v2[] = anotherString.value;
  11. int i = 0;
  12. while (n-- != 0) {
  13. if (v1[i] != v2[i])
  14. return false;
  15. i++;
  16. }
  17. return true;
  18. }
  19. }
  20. return false;
  21. }
  1. public int hashCode() {
  2. int h = hash;
  3. if (h == 0 && value.length > 0) {
  4. char val[] = value;
  5. for (int i = 0; i < value.length; i++) {
  6. h = 31 * h + val[i];
  7. }
  8. hash = h;
  9. }
  10. return h;
  11. }
  12. //h=val[0]*31^(n-1) + val[1]*31^(n-2) + ... + val[n-1]

先调用这个元素的 hashCode 方法,然后根据所得到的值计算出元素应该在数组的位置。如果这个位置上没有元素,那么直接将它存储在这个位置上;

如果这个位置上已经有元素了,那么调用它的equals方法与新元素进行比较:相同的话就不存了,否则,将其存在这个位置对应的链表中

  1. int i2 = 128;
  2. int i3 = 128;
  3. Integer i4 = new Integer(128);
  4. Integer i5 = 128;
  5. System.out.println(i2==i3);//true
  6. //i4、i5在赋值的时候,会自动把128装箱成Integer类型,接着使用==比较的时候,i4、i5又会自动拆箱成int,所以实质上还是两个int在比较
  7. System.out.println(i2==i4);//true
  8. System.out.println(i2==i5);//true
  9. System.out.println(i4==i5);//false
  10. /*Integer i = 2
  11. 将 2 通过 Integer.valuesOf方法生成一个Integer对象,再将地址赋给 i在-128~127之间的数,那么返回Integer是被 Integer cache[]数组,否则是new Integer(i)*/
  12. Integer i=2;
  13. Integer j=2;
  14. Integer m=new Integer(2);
  15. Integer i1=156;
  16. Integer j1=156;
  17. Integer m1=new Integer(156);
  18. Long g=new Long(2);
  19. System.out.println(i==j);//true
  20. System.out.println(i1==j1);//false
  21. System.out.println(i==m);//false
  22. System.out.println(i.equals(m));//true
  23. System.out.println(i==2);//true
  24. //调用了m的实例方法Integer intValue()
  25. //int i=2;i==m
  26. System.out.println(m==2);//true
  27. System.out.println(m1==156);//true
  28. System.out.println(g==2L);//true
  29. System.out.println(g==2);//true
  30. System.out.println(i.equals(g));//false
  31. //g.equals(Integer.valueOf(2))
  32. System.out.println(g.equals(2));//false
  33. System.out.println(g.equals(2L));//true
  • Integer类的一些 == 比较,要注意到自动装箱机制的存在,(-128~127)这个范围之间,如果是通过 == 赋值的话,是返回IntegerCache[ ] 数组中的引用。超过这个范围,则返回一个新的 Integer对象
  • new Integer 和 数值( 比如说 2 比较的时候 ),分析字节码得知,new Integer对象在比较的时候调用到了intValue()方法,其实比较的是数值是否相等
  • 每个对象重写 equals()方法,对象的equals方法是用来比较同类型的两个对象内容是否相等的。而Integer.equals( Long )肯定是不相等的
  • 基本类型对应的包装类中,ByteShortIntegerLongCharacter的缓存池都是[-128, 127]Boolean的缓存池比较特殊,只有true和false两个Boolean对象。
  • int对应的装箱方法是Integer#valueOf,拆箱方法是Integer#intValue()
  1. String s1 = "Java";
  2. String s2 = "Java";
  3. String s3 =new String( "Java");
  4. String s4 = new String("Java");
  5. System.out.println(s1 == s2);//true
  6. System.out.println(s3 == s4);//false
  7. System.out.println(s1 == s3);//false
  8. String s5="hello";
  9. String s6="world";
  10. String s7="helloworld";
  11. String s8=s5+"world";
  12. String s9=s5+s6;
  13. //final修饰的s1在编译期就可以识别 s11="hello"+"world"
  14. final String s10 = "hello";
  15. String s11=s10+"world";
  16. System.out.println("hello"+"world"==s7);//true
  17. System.out.println(s7==s8);//false
  18. System.out.println(s7==s9);//false
  19. System.out.println(s8==s9);//false
  20. System.out.println(s7==s11);//true
  21. //在JDK1.6中,会得到两个false 常量池在永久代分配内存,永久代和Java堆的内存是物理隔离的。 intern方法虚拟机会在常量池中复制该字符串,并返回引用;如果已经存在该字符串了,则直接返回这个常量池中的这个常量对象的引用。
  22. //在JDK7、8中会得到一个true和一个false intern方法 如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池当然这个时候,常量池被从方法区中移出来到了堆中。
  23. String str1 = new StringBuilder("计算机").append("软件").toString();
  24. System.out.println(str1.intern() == str1);//true
  25. //由于JVM的 特殊性在JVM启动的时候调用了一些方法,在常量池中已经生成了“java”字符串常量。
  26. String str2 = new StringBuilder("ja").append("va").toString();
  27. System.out.println(str2.intern() == str2);//false
  28. //str3的时候,只有一个堆的String对象,此时常量池中没有“str01”这个常量对象,str4此时在常量池中创建“str01”,str3.intern()返回的str4的引用
  29. String str3 = new String("str")+new String("01");
  30. String str4 = "str01";
  31. System.out.println(str3==str4);//false
  32. System.out.println(str3.intern()==str3);//false
  33. System.out.println(str3.intern()==str4);//true
  34. System.out.println(str3==str4);//false
  35. //str5的时候,只有一个堆的String对象,然后调用intern,常量池中没有“str02”这个常量对象,于是常量池中生成了一个对这个堆中string对象的引用。然后给str6赋值的时候,因为是带引号的,所以去常量池中找,发现有这个常量对象,就返回这个常量对象的引用,也就是str5引用所指向的堆中的String对象的地址。
  36. String str5 = new String("str")+new String("02");
  37. str5.intern();
  38. String str6 = "str02";
  39. System.out.println(str5==str6);//true
  • String d = “big flower1”+”big flower1”在字节码层面就相当与 String d =big flower1big flower1,即两个或者两个以上的字符串常量相加,在预编译的时候“+”会被优化,相当于把两个或者两个以上字符串常量自动合成一个字符串常量
  • JVM为了优化非纯常量的字符串相加,在“+”这个操作符的重载中自动引入了StringBuilder类
  • 有 new String(“ big flower”), 那么使用 == 去比较,一定是返回false,永远都是不同的对象。只能通过equals去比较两个对象的内容
  • 使用 == 比较两个字符串的时候,要注意到常量池的存在
  • javap -v XXX 的方式查看编译的代码发现 new String 首次会在字符串常量池中创建此字符串,那也就是说,通过 new 创建字符串的方式可能会创建 1 个或 2 个对象

基础一 - 图1

基础一 - 图2

6.String,Stringbuffer,StringBuilder的区别

String

  • String是final类,不能被继承(String 类作为最常用的类之一,禁止被继承和重写,可以提高效率;String 类中有很多调用底层的本地方法,调用了操作系统的 API,如果方法可以重写,可能被植入恶意代码,破坏程序。)
  • String类一旦创建值就不可以修改(Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程)
  • String重写了equals()方法和hashCode()方法

StringBuffer

  • 继承AbstractStringBuilder,是可变类
  • StringBuffer是线程安全,执行效率低
  • 可以通过append方法动态构造数据

StringBuilder

  • 继承自AbstractStringBuilder,是可变类
  • StringBuilder是非线程安全,执行效率高

对字符串进行修改的时候,特别是字符串对象经常改变的情况下,需要使用 StringBuffer 和 StringBuilder 类。

在以下的字符串对象生成中, String 效率是远要比 StringBuffer 快。

//String S1 = “This is only a simple test”; 
String S1 = "This is only a" + " simple" + " test";
StringBuffer Sb = new StringBuffer("This is only a").append(" simple").append(" test");

7.创建一个java.lang.String类,这个类是否可以被类加载器加载?

不可以。因为JDK处于安全性的考虑,基于双亲委派模型,优先加载JDK的String类,如果java.lang.String已经加载,便不会再次被加载。

String 类常用方法

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

将字符串反转

  • 使用 StringBuilder 或 StringBuffer 的 reverse 方法,本质都调用了它们的父类 AbstractStringBuilder 的 reverse 方法实现。(JDK1.8)
  • 使用chatAt函数,倒过来输出

8.Java创建对象方式

  • 用new语句创建对象ObjectName obj = new ObjectName(); new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存在栈内存中)
  • 使用反射的Class类的newInstance()方法 ObjectName obj = ObjectName.class.newInstance();
  • 使用反射的Constructor类的newInstance()方法 ObjectName obj = ObjectName.class.getConstructor.newInstance();
  • 调用对象的clone()方法 ObjectName obj = obj.clone();
  • 运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法
  • 使用Unsafe sun.misc.Unsafe 该类主要提供一些直接访问系统内存资源
package com.lymn
import java.io.Serializable;
import java.util.Objects;

public class Employee implements Serializable, Cloneable {
    private static final long serializableUID = 1L;
    private String name;
    public Employee() {
        System.out.println("Employee Constructor Called...");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Employee employee = (Employee) o;
        return Objects.equals(name, employee.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
    @Override
    public String toString() {
        return "Employee{" + "name='" + name + '\'' + '}';
    }
    @Override
    public Employee clone() {
        Employee clone = null;
        try {
            clone = (Employee) super.clone();
        } catch (CloneNotSupportedException e) {

        }
        return clone;
    }
}
package com.lymn;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import sun.misc.Unsafe;

public class ObjectCreate {
    private static final String FILE_NAME = "employee.obj";

    public static void main(String[] args) throws Exception {
        // 使用 new关键字 创建对象
        Employee employee = new Employee();
        employee.setName("张三");
        System.out.println("new对象方法:" + employee);
        // 使用 Class类的 newInstance()方法
        // Employee employee2 = (Employee) Class.forName("Employee").newInstance();
        Employee employee2 = Employee.class.newInstance();
        employee2.setName("xxx2");
        System.out.println("Class类的newInstance()方法:" + employee2);

        // 使用 Constructor类的newInstance()方法
        Employee employee3 = Employee.class.getConstructor().newInstance();
        employee3.setName("xxx3");
        System.out.println("Constructor类的newInstance()方法:" + employee3);

        // 使用 clone()方法:类必须实现Cloneable接口,并重写其clone()方法
        Employee employee4 = (Employee) employee.clone();
        // employee4.setName("xxx4");
        System.out.println("对象clone()方法:" + employee4);

        // 使用 反序列化ObjectInputStream 的readObject()方法:类必须实现 Serializable接口
        // 序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME))) {
            oos.writeObject(employee);
        }
        // 反序列化
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME))) {
            Employee employee5 = (Employee) ois.readObject();
            System.out.println("反序列化:" + employee5);
        }
        //unSafe方法
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        Employee employee6 = (Employee) unsafe.allocateInstance(Employee.class);
        employee6.setName("xxx4");
        System.out.println("unSafe方法:" + employee6);

    }
========================================================
     /*  Employee Constructor Called...
        new对象方法:Employee{name='张三'}
        Employee Constructor Called...
        Class类的newInstance()方法:Employee{name='xxx2'}
        Employee Constructor Called...
        Constructor类的newInstance()方法:Employee{name='xxx3'}
        对象clone()方法:Employee{name='张三'}
        反序列化:Employee{name='张三'}
        unSafe方法:Employee{name='xxx4'}*/
}

9.访问修饰符public,private,protected,以及default

基础一 - 图3

private:在同一类可见,使用对象:变量,方法,注意不能修饰类

default(缺省,什么都不写):在同个包内可见,不使用任何修饰符。使用对象:类,接口,变量,方法

protected:对同一包内的所有子类可见。使用对象:变量,方法,注意不能修饰类(外部类)

public:对所有类可见,使用对象:类,接口,变量,方法

10.重载和重写的区别

  • 方法的重载和重写是实现多态的方式,区别在于前者实现的是编译时的多态,后者是运行的多态性
  • 重载:发生在同一个类中,方法名相同但参数列表不同(个数、类型、顺序),与方法返回值和修饰符无关,即重载的方法不能根据返回类型进行区分
  • 重写:发生在父子类,方法名、参数列表相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏替换原则);如果父类方法访问修饰符为private则子类中就不是重写。

11.抽象类和接口

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

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

  • 接口和抽象类都不能实例化
  • 都包含抽象方法,其子类都必须覆写这些抽象方法
  • 抽象类要被子类继承,接口要被子类实现
  • 抽象类的关键字是abstract,接口的关键字是interface
  • 一个类可以实现多个接口,但是只能继承一个父类
  • 抽象类可以有构造方法,接口中不能有构造方法
  • 抽象类中可以有普通成员变量,接口中变量只能是公共静态常量
  • 接口只能做方法声明,抽象类中可以作方法声明,也可以做方法实现
  • 抽象类可以有 main 方法,并且能运行,接口不能有 main 方法

12.普通类和抽象类

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

13.final, finally, finalize 的区别

  • final 用于修饰属性、方法和类,分别表示属性不能被重新赋值, 方法不可被覆盖, 类不可被继承
  • finally 是异常处理语句结构的一部分,一般以try-catch-finally出现,finally代码块表示总是被执行,一般用来存放一些关闭资源的代码
  • finalize 是Object类的一个方法,该方法一般由垃圾回收器来调用,调用System.gc() 方法时,由垃圾回收器调用finalize()方法回收垃圾

14.Java支持多继承?

  • 安全性的考虑,如果子类继承的多个父类里面有相同的方法或者属性,子类将不知道具体要继承哪个
  • Java提供了接口和内部类以达到实现多继承功能,弥补单继承的缺陷

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

  • Java5以前,只能是byte,short,int,char
  • Java5开始,加入枚举类型(enum)
  • Java7开始,加入String
  • switch可作用于char byte short int对应的包装类
  • switch不可作用于long double float boolean,以及他们的包装类

16.常见知识

  • Math.round(11.5) = 12
  • Math.round(-11.5) = -11
  • float f =(float)3.4; 或者写成 float f =3.4F
  • 左移2<<3==》2 乘以 8
  • short s1 = 1; s1 = (short) (s1 + 1);或者short s1 = 1; s1 += 1;
  • 逻辑与&、逻辑或|与短路与&&、短路||(&&左边表达式值是 false)
public static void main(String[] args) {
        int i = 1;
        i = i++;
        int j = i++;
        int k = i + ++i * i++;
        System.out.println("i=" + i);
        System.out.println("j=" + j);
        System.out.println("k=" + k);
    }
————————————————
i=4
j=1
k=11

17.object中定义了哪些方法?

  • getClass(); 获取类结构信息
  • hashCode() 获取哈希码
  • equals(Object) 默认比较对象的地址值是否相等,子类可以重写比较规则
  • clone() 用于对象克隆
  • toString() 把对象转变成字符串
  • notify() 多线程中唤醒功能
  • notifyAll() 多线程中唤醒所有等待线程的功能
  • wait() 让持有对象锁的线程进入等待
  • wait(long timeout) 让持有对象锁的线程进入等待,设置超时毫秒数时间
  • wait(long timeout, int nanos) 让持有对象锁的线程进入等待,设置超时纳秒数时间

18.我们能将int强制转换为 byte类型的变量吗?如果该值大于byte 类型的范围,将会出现什么现象?

可以,我们可以做强制转换,但是在Java中,int是32位,byte是8位,如果强制做转化,int类型的高24位将会被丢弃。

public class Test {
    public static void main(String[] args)  {
        int a =129;
        byte b = (byte) a;
        System.out.println(b);
        int c =10;
        byte d = (byte) c;
        System.out.println(d);
    }
}
输出:
-127
10

19.this和super关键字

  • this调用本类构造构造,必须放在构造方法的首行;super调用父类构造,必须放在子类构造方法首行
  • this访问本类中的属性、方法,super访问父类中的属性、方法
  • this() 和super() 不能存在于同一个构造函数中,this()和super()都必须写在构造函数的第一行
  • this和super不能用于static修饰的变量,方法,代码块。因为this和super都是指的是对象(实例)

20.static

  • static修饰的变量或者方法独立于该类的任何对象,被类的实例对象共享
  • 类第一次加载时,会加载static修饰的部分,并且只会在类第一次加载时进行初始化
  • 被static修饰的变量或者方法优于对象存在,即使没有对象,也可以去访问
  • 静态只能访问静态
  • 非静态即可以访问非静态,也可以访问静态

21.static环境中访问非static变量?

static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。因为静态的成员属于类,随着类的加载而加载到静态方法区内存,当类加载时,此时不一定有实例创建,没有实例,就不可以访问非静态的成员。类的加载先于实例的创建,因此静态环境中,不可以访问非静态!

22.是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?

不可以。

  • 非static方法是要与对象实例息息相关的,必须在创建一个对象后,才可以在该对象上进行非static方法调用,而static方法跟类相关的,不需要创建对象,可以由类直接调用。
  • 当一个static方法被调用时,可能还没有创建任何实例对象,如果从一个static方法中发出对非static方法的调用,那个非static方法是关联到哪个对象上的呢?这个逻辑是不成立的
  • 因此,一个static方法内部不可以发出对非static方法的调用。

23.类的实例化顺序

类实例化顺序为: 父类静态代码块/静态域->子类静态代码块/静态域 -> 父类非静态代码块 -> 父类构造器 -> 子类非静态代码块 -> 子类构造器

public class Parent {
    {
        System.out.println("父类非静态代码块");
    }
    static {
        System.out.println("父类静态块");
    }
    public Parent() {
        System.out.println("父类构造器");
    }
}
public class Son extends Parent {
    public Son() {
        System.out.println("子类构造器");
    }
    static {
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类非静态代码块");
    }
}
public class Test {
    public static void main(String[] args) {
        Son son = new Son();
    }
}
public class Father {
    private int i = test();
    private static int j = method();

    static{
        System.out.println("(1)");
    }
    Father() {
        System.out.println("(2)");
    }
    {
        System.out.println("(3)");
    }
    public int test(){
        System.out.println("(4)");
        return 1;
    }
    public static int method() {
        System.out.println("(5)");
        return 1;
    }
}
public class Son extends Father {
    private int i = test();
    private static int j = method();
    static {
        System.out.println("(6)");
    }
    Son() {
        super();
        System.out.println("(7)");
    }
    {
        System.out.println("(8)");
    }
    public int test(){
        System.out.println("(9)");
        return 1;
    }
    public static int method() {
        System.out.println("(10)");
        return 1;
    }

    public static void main(String[] args) {
        Son son = new Son();
        System.out.println();
        Son son1 = new Son();
    }
}
————————————————
(5)
(1)
(10)
(6)
(9)
(3)
(2)
(9)
(8)
(7)

(9)
(3)
(2)
(9)
(8)
(7)

24.构造器是否可被重写?

构造器是不能被继承的,因为每个类的类名都不相同,而构造器名称与类名相同,所以谈不上继承。 又由于构造器不能被继承,所以相应的就不能被重写了。构造器没有返回值,但不能用void声明构造函数。

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

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

26.如何跳出当前的多重嵌套循环

在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;
            }
        }
    }
}

27.如何实现对象克隆?

  • 结合序列化(JDK java.io.Serializable 接口、JSON格式、XML格式等),完成深拷贝
  • 实现 Cloneable 接口,重写 clone() 方法。
  • Object 的 clone() 方法是浅拷贝,即如果类中属性有自定义引用类型,只拷贝引用,不拷贝引用指向的对象。
  • 对象属性的Class 也实现 Cloneable 接口,在克隆对象时也手动克隆属性,完成深拷贝
package com.lymn;

public class Text implements Cloneable{
    private int age;
    private Name name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Name getName() {
        return name;
    }
    public void setName(Name name) {
        this.name = name;
    }
    @Override
    protected Object clone(){
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static void main(String[] args){
         Name name1=new Name();
         name1.setName("name1");
         Text t1=new Text();
         t1.setAge(12);
         t1.setName(name1);
         Text t2=(Text) t1.clone();
         System.out.println(t2.getName().getName());
         name1.setName("name2");
         System.out.println(t2.getName().getName());
        }
}
class Name{
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
-------------
name1
name2
因为只是直接调用父类的clone方法,没有对成员属性进行处理,所以在修改t1属性name的值时,t2属性name的值也会随之改变。
package com.linln.boot;

public class Text implements Cloneable{
    private int age;
    private Name name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Name getName() {
        return name;
    }
    public void setName(Name name) {
        this.name = name;
    }
    @Override
    protected Object clone(){
        Text text=null;
        try {
            text=(Text) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        text.setName((Name) text.getName().clone());
        return text;
    }
    public static void main(String[] args){
         Name name1=new Name();
         name1.setName("name1");
         Text t1=new Text();
         t1.setAge(12);
         t1.setName(name1);
         Text t2=(Text) t1.clone();
         System.out.println(t2.getName().getName());
         name1.setName("name2");
         System.out.println(t2.getName().getName());
        }
}
class Name implements Cloneable{
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    protected Object clone(){
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
----------
name1
name1

28.方法参数传递机制

package com.linln.boot;

import java.util.Arrays;

public class Exam {
    public static void main(String[] args) {
        int i = 1;
        String str = "hello";
        Integer num = 200;
        int[] arr = { 1, 2, 3, 4, 5 };
        MyData my = new MyData();

        change(i, str, num, arr, my);

        // arr my变了
        System.out.println("i= " + i);//1
        System.out.println("str= " + str);//hello
        System.out.println("num= " + num);//200
        System.out.println("arr= " + Arrays.toString(arr));//[2, 2, 3, 4, 5]
        System.out.println("my.a= " + my.a);//11
    }

    public static void change(int j, String s, Integer n, int[] a, MyData m) {
        j += 1;
        s += "world";
        n += 1;
        a[0] += 1;
        m.a += 1;
    }
}

class MyData {
    int a = 10;
}
--------
i= 1
str= hello
num= 200
arr= [2, 2, 3, 4, 5]
my.a= 11

基础一 - 图4

29.内部类

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

四种:成员内部类,局部内部类,匿名内部类,静态内部类

静态内部类:定义在类内部的静态类,静态内部类可以访问外部类的所有静态变量,而不可以访问外部类的非静态变量。

public class Outer {
    private static int radius = 1;
    static class StaticInner {
        public void visit() {
            System.out.println("visit outer static  variable:" + radius);
        }
    }
     public static void main(String[] args) {
         //静态内部类的创建方式(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);
        }
    }
    public static void main(String[] args) {
         //调用方式,依赖于外部类的实例(外部类实例.new 内部类())
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.visit();
     }
}
public class Outer {
    private int age = 12;
    class Inner {
        private int age = 13;
        public void print() {
            int age = 14;
            System.out.println("局部变量:" + age);//14
            System.out.println("内部类变量:" + this.age);//13
            System.out.println("外部类变量:" + Outer.this.age);//12
        }
    }
    public static void main(String[] args) {
        Outer.Inner in = new Outer().new Inner();
        in.print();
    }
}

静态内部类与非静态内部类(成员内部类)有什么区别

  • 静态内部类可以有静态成员(方法,属性),而非静态内部类则不能有静态成员(方法,属性)
  • 静态内部类只能够访问外部类的静态成员和静态方法,而非静态内部类则可以访问外部类的所有成员(方法,属性)
  • 实例化静态内部类与非静态内部类的方式不同
  • 调用内部静态类的方法或静态变量,可以通过类名直接调用

局部内部类:定义在方法中的内部类。定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局布类只能访问外部类的静态变量和方法。

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);
            }
        }
        //调用方式:new 内部类(),只能在对应方法内定义
        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);
            }
        }
        //调用方式:new 内部类(),只能在对应方法内定义
        Inner  inner = new Inner();
        inner.fun();
    }
}

匿名内部类:没有名字的内部类。匿名内部类必须继承一个抽象类或者实现一个接口;匿名内部类不能定义任何静态成员和静态方法;当所有方法的形参需要被匿名内部类使用时,必须声明为final;匿名内部类不能访问外部类方法中的局部变量,除非该变量被声明为final类型;匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

public class Outer {

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

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

  • 因为生命周期不一致:局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量,就会出错。
  • 加了final,可以确保局部内部类使用的变量与外层的局部变量区分开

30.日期类

java.util.Date类表示特定的日期和时间,精确到毫秒。

//返回当前时间,默认的输出格式为Tue Aug 13 21:33:30 CST 2019
Date date = new Date();
Date()//生成一个代表当前日期的Date对象。该构造器在底层调用System.currentTimeMillis()获得long长整数作为日期参数。
Date(long date)//根据指定的long型整数生成一个Date对象。该构造器的参数表示创建的Date对象和GMT1970年1月1日00:00:00之间的时间差,以毫秒为单位。
boolean after(Date when)//测试该日期是否在指定的日期when之后。
boolean before(Date when)//测试该日期是否在指定的日期when之前。
long getTime()//返回该时间对应的long 型整数,即从GMT 1970-01-01 00:00:00到该Date对象之间的时间差,以毫秒为单位。
void setTine(long time) //设置该Date对象的时间。
//Date类有三个子类,都位于java.sql包中
java.sql.Date //日期,YYYY-MM-DD
java.sql.Time //时间,HH:mm:ss
java.sql.Timestamp //时间戳,YYYY-MM-DD HH:mm:ss.SSS,精确到毫秒
//Date类不推荐使用

java.util.Calendar类是表示日期时间的抽象类,精确到毫秒。

Calendar cal = Calendar.getInstance();
//从Calendar对象中取出Date对象
Date date = cal.getTime();
//通过Date对象获取对应的Calendar对象
//由于Calendar没有构造器接收Date对象
//所以必须先获得一个Calendar实例,然后调用其setTime()方法
Calendar cal2 = Calendar.getInstance();
cal2.setTime(date);

 //取出年
System.out.print("取出年:");
System.out.println(cal.get(Calendar.YEAR));
//取出月:当前月减1
System.out.print("取出月:");
System.out.println(cal.get(Calendar.MONTH));
//取出日
System.out.print("取出日:");
System.out.println(cal.get(Calendar.DATE));
//分别设置年、月、日、时、分、秒
System.out.print("设置时间:");
cal.set(2019, 02, 22, 22, 06, 34);
System.out.println(cal.getTime());
//将Calendar的年前推1年
System.out.print("将Calendar的年前推1年:");
cal.add(Calendar.YEAR, -1);
System.out.println(cal.getTime());
//将Calendar的月前推3个月
System.out.print("将Calendar的月前推3个月:");
cal.add(Calendar.MONTH, -3);
System.out.println(cal.getTime());

add:当被修改的字段超出它允许的范围时,会发生进位,即上一级字段也会增大;如果下一级字段也需要改变,则该字段会修正到变化最小的值。

roll:当被修改的字段超出它允许的范围时,上一级字段也不会增大,即不会发生进位;roll()下一级字段的处理与add()相同,都会修正到该字段变化的最小值。

Calendar cal = Calendar.getInstance();

cal.set(2008,7,23,0,0,0);//2008-8-23
cal.add(Calendar.MONTH,6);//2008-8-23 =>2009-2-23
cal.roll(Calendar.MONTH,6);//2008-8-23 =>2008-2-23

cal.set(2008,7,31,0,0,0);//2008-8-31
//因为进位后月份变为2,而2月没有31日,自动变成28
cal.add(Calendar.MONTH,6);//2008-8-31 =>2009-2-28
cal.roll(Calendar.MONTH,6);//2008-8-31 =>2008-2-29

通过set()方法设置某一个字段的值得时候,该字段的值不会立马修改,直到下次调用get()、getTime()等时才会重新计算日历的时间。延迟修改的优势是多次调用set()方法不会触发多次不必要的计算。

Calendar cal = Calendar.getInstance();
cal.set(2003,7,31);//2003-8-31
//将月份设为9,但9月31不存在
//如果立即修改,系统会把cal自动调整到10月1日
cal.set(Calendar.MONTH,8);
//下面代码输出了10月1日
System.out.println(cal.getTime());//(1)
//设置DATE字段为5
cal.set(Calendar.DATE, 5);//(2)
//输出了10月5日
System.out.println(cal.getTime());//(3)
//如果将(1)处的代码注释掉,打印结果为9月5日

java.text.DateFormat类是表示日期时间格式的抽象类。在创建 DateFormat 对象时不能使用 new 关键字,而应该使用 DateFormat 类中的静态方法getDateInstance()方法。

 Date now = new Date(); // 创建一个Date对象,获取当前时间
//yyyy年MM月dd日 HH时mm分ss秒
DateFormat df = new SimpleDateFormat("今天是 " + "yyyy 年 MM 月 dd 日 E HH 点 mm 分 ss 秒");
System.out.println(df.format(now));//将当前时间袼式化为指定的格式
//今天是 2021 年 05 月 09 日 星期日 13 点 55 分 48 秒

SimpleDateFormat 自定义格式中常用的字母及含义如下表所示。

字母 含义 示例
y 年份。一般用 yy 表示两位年份,yyyy 表示 4 位年份 使用 yy 表示的年扮,如 11; 使用 yyyy 表示的年份,如 2011
M 月份。一般用 MM 表示月份,如果使用 MMM,则会 根据语言环境显示不同语言的月份 使用 MM 表示的月份,如 05; 使用 MMM 表示月份,在 Locale.CHINA 语言环境下,如“十月”;在 Locale.US 语言环境下,如 Oct
d 月份中的天数。一般用 dd 表示天数 使用 dd 表示的天数,如 10
D 年份中的天数。表示当天是当年的第几天, 用 D 表示 使用 D 表示的年份中的天数,如 295
E 星期几。用 E 表示,会根据语言环境的不同, 显示不 同语言的星期几 使用 E 表示星期几,在 Locale.CHINA 语 言环境下,如“星期四”;在 Locale.US 语 言环境下,如 Thu
H 一天中的小时数(0~23)。一般用 HH 表示小时数 使用 HH 表示的小时数,如 18
h 一天中的小时数(1~12)。一般使用 hh 表示小时数 使用 hh 表示的小时数,如 10 (注意 10 有 可能是 10 点,也可能是 22 点)
m 分钟数。一般使用 mm 表示分钟数 使用 mm 表示的分钟数,如 29
s 秒数。一般使用 ss 表示秒数 使用 ss 表示的秒数,如 38
S 毫秒数。一般使用 SSS 表示毫秒数 使用 SSS 表示的毫秒数,如 156

为了弥补传统Java对日期、时间处理的不足,Java8提供了一套全新的日期时间库。Java8专门新增了一个java.time包。

Clock:该类用于获取指定时区的当前日期、时间。该类可以取代System类的currentTimeMillis()方法

//获取当前Clock
Clock clock = Clock.systemUTC();
//获取clock对应的毫秒数
//等于System.currentTimeMillis()
System.out.println(clock.millis());

Duration:该类代表持续时间

Duration d = Duration.ofSeconds(4600);
System.out.println("4600秒="+d.toMinutes()+"分");//600秒=76分
System.out.println("4600秒="+d.toHours()+"时");//600秒=1时
System.out.println("4600秒="+d.toDays()+"天");//600秒=0天

Instant:代表一个具体的时刻,可以精确到纳秒

 //获取当前时间
 Instant instant = Instant.now();
 System.out.println(instant);
 //instant增加600秒
 Instant instant2 = instant.plusSeconds(600);
 //在当前时刻基础上600s
 Instant instant3 = instant.minusSeconds(600);
 System.out.println(instant2);
 System.out.println(instant3);

LocalDate:代表不带时区的日期

 //获取当前日期
LocalDate date = LocalDate.now();
System.out.println(date);//2021-05-09
//当前日期加上2天
LocalDate date2 = date.plusDays(2);
//当前日期减去2天
LocalDate date3 = date.minusDays(2);
System.out.println(date2);//2021-05-11
System.out.println(date3);//2021-05-07

LocalTime:代表不带时区的时间

//获取当前时间
LocalTime time = LocalTime.now();
System.out.println(time);//13:31:34.865
//当前时间加上2分钟
LocalTime time2 = time.plusMinutes(2);
//当前时间减去2分钟
LocalTime time3 = time.minusMinutes(2);
System.out.println(time2);//13:33:34.865
System.out.println(time3);//13:29:34.865

LocalDateTime:该类代表不带时区的日期、时间,如:2019-02-24T10:20:09。

MonthDay:该类仅代表月日,如:—09-20。

Year:该类仅代表年,如:2019。

YearMonth:该类仅代表年月,如:2019-02。

ZonedDateTime:该类代表一个时区化的日期、时间。

ZonedId:该类代表一个时区。

DayOfWeek:该类是一个枚举类,定义了周日到周六的枚举值。

Month:该类是一个枚举类,定义了一月到十二月的枚举值。