JAVA问题

JDK 和 JRE 有什么区别?

https://blog.csdn.net/singit/article/details/62040688
https://www.cnblogs.com/lkboy/p/4159379.html

JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。

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

一. JDK——开发环境

https://zhuanlan.zhihu.com/p/46562903 JDK

JDK是Sun公司(已被Oracle收购)针对Java开发员的软件开发工具包。是提供给程序员使用的。

JDK主要包含三部分,

第一部分就是Java运行时环境,JVM。

第二部分就是Java的基础类库,这个类库的数量还是非常可观的。

第三部分就是Java的开发工具,它们都是辅助你更好的使用Java的利器。

看一下JDK的安装目录。在目录下面有六个文件夹、一个src类库源码压缩包、和其他几个声明文件。

其中,真正在运行java时起作用的是以下四个文件夹:bin、include、lib、 jre。

现在我们可以看出这样一个关系,JDK包含JRE,而JRE包含JVM。

bin:最主要的是编译器(javac.exe)
include:java和JVM交互用的头文件
lib:类库
jre:java运行环境(注意:这里的bin、lib文件夹和jre里的bin、lib是不同的)

总的来说JDK是用于java程序的开发,而jre则是只能运行class而没有编译的功能。

二. JRE——运行环境

JRE是java运行时环境,包含了java虚拟机,java基础类库。是使用java语言编写的程序运行所需要的软件环境,是提供给想运行java程序的用户使用的。

JRE根据不同操作系统(如:windows,linux等)和不同JRE提供商(IBM,ORACLE等)有很多版本,最常用的是Oracle公司收购SUN公司的JRE版本。

  1. JDK中的JRE
    文件夹中jdk中包含的文件夹jre,在jrebin目录里有个jvm.dll,JRE是运行环境,运行在JVM虚拟机上,jrelib目录中放的是一些JAVA类库的class文件,已经打包成jar文件。

  2. 独立的JRE
    单独的JRE里自然也是有JVM的。

光有JVM还不能成class的执行,因为在解释class的时候JVM需要调用解释所需要的类库lib。

在JDK的安装目录里你可以找到jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib和起来就称为jre。

所以,在你写完java程序编译成.class之后,你可以把这个.class文件和jre一起打包发给朋友,这样你的朋友就可以运行你写程序了。

三. JVM——转换环境

JVM就是java虚拟机的缩写。

它是整个java实现跨平台的最核心的部分,所有的 java 程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行,也就是说class并不直接与机器的操作系统相对应,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行。

四. javac

javac是java语言编程编译器。全称java compiler. javac工具读有java语言编写的类和接口的定义,并将它们编译成字节代码的class文件。

javac 编译.java文件成为.class文件

为什么Sun要让JDK安装两套相同的JRE?

这是因为JDK里面有很多用Java所编写的开发工具(如javac.exe、jar.exe等),而且都放置在 \lib\tools.jar 里。

从下面例子可以看出,先将tools.jar改名为tools1.jar,然后运行javac.exe,显示如下结果: Exception in thread “main” java.lang.NoClassDefFoundError: com/sun/tools/javac /Main 这个意思是说,你输入javac.exe与输入 java -cp c:\jdk\lib\tools.jar com.sun.tools.javac.Main 是一样的,会得到相同的结果。

从这里我们可以证明javac.exe只是一个包装器(Wrapper),而制作的目的是为了让开发者免于输入太长的指命。而且可以发现\lib目录下的程序都很小,不大于2 9K,从这里我们可以得出一个结论。

就是JDK里的工具几乎是用Java所编写,所以也是Java应用程序,因此要使用JDK所附的工具来开发Java程序,也必须要自行附一套JRE才行,所以位于C:\Program Files\Java目录下的那套JRE就是用来运行一般Java程序用的。

如果一台电脑安装两套以上的JRE,谁来决定呢?

这个重大任务就落在java.exe身上。Java.exe的工作就是找到合适的JRE来运行Java程序。

Java.exe依照底下的顺序来查找JRE:自己的目录下有没有JRE;父目录有没有JRE;查询注册表: [HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment] 所以java.exe的运行结果与你的电脑里面哪个JRE被执行有很大的关系。

== 和 equals 的区别是什么?

一. ==

比较的是值是否相等

  1. 如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等
  2. 如果作用于引用类型的变量,则比较的是所指向的对象的地址

二. equals

比较的是是否是同一个对象

equals方法不能作用于基本数据类型的变量

  1. 如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址
  2. 诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容

equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,所以说所有类中的equals()方法都继承自Object类

在没有重写equals()方法的类中,调用equals()方法其实和使用==的效果一样,也是比较的是引用类型的变量所指向的对象的地址

不过,Java提供的类中,有些类都重写了equals()方法,重写后的equals()方法一般都是比较两个对象的值,比如String类。

Object类equals()方法源码:

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

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

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

https://blog.csdn.net/weixin_38405253/article/details/91922340

不一定

  1. HashCode的存在是为了查找的快捷性,用于散列存储结构中确定对象的储存地址。
  2. 如果两个对象equals相等,则HashCode也一定相等
  3. 如果equals方法被重写,则HashCode也应该被重写(这是为了避免出现两个对象相同,但是hashCode值却不同的情况)
  4. 如果两个对象的HashCode相等,equals方法不一定相等

HashCode是Object的方法。查看Object源码,找到HashCode,可以看到返回值是int,是native方法,不是Java原生方法,是由其他语言编写的。equals返回值是布尔值,默认实现是==。

严格来讲他们两个直接没有任何关系,因为我们可以随意的重写这两个方法的实现。

定义对象H,他的hashCode就是固定的返回一个值1。这样的话我们创建两个对象,他们的hashCode一定是相等的;而他的equals因为是两个对象,内存地址不一样,所以返回false。

class H{
	@Override
	publice int hashCode(){
		return 1;
	}
}
H h1 = new H();
H h2 = new H();
System.out.println(h1.hashCode() == h2.hashCode()); //true
System.out.println(h1.equals(h2)); //false

另一方面,如果我们重写的是equals方法,并且返回固定值true。我们同样创建两个对象,他们的hashCode返回值是false,但是一定equals。因为重写了equals方法。

class H{
	@Override
	publice boolean equals(Object o){
		return true;
	}
}
H h1 = new H();
H h2 = new H();
System.out.println(h1.hashCode() == h2.hashCode()); //false
System.out.println(h1.equals(h2)); //true

上面这两个例子可以看出,hashcode和equals其实没有必然的联系。

规范

但是在重写hashCode和equals方法的时候,我们应该满足这样的规范。

  1. hashCode和equals返回值应该是稳定的,不应有随机性。
  2. 俩对象==返回true则这两个对象的equals也应该返回true。
  3. 俩对象equals则这两个对象的hashCode应该相等。
// 不规范但是能照常运行
class H{
	@Override
	publice int hashCode(){
		return Math.random()>0.5 ? 2:1;
	}
}

为什么要有这样的规范呢?这就涉及到了hash存储,以hashMap为例:

在hashMap中,我们通过hashCode得到的值来计算出在数组中的下标。

例如下标等于1,那么就在1这个桶中去找对应的元素;1这个桶中存储的可能是链表或者是红黑树;假如说是一个链表,我们就要在这个链式结构中挨个去查找,是否是我们想要的元素,而挨个查找的时候我们就需要运用equals方法,对比每个节点的key跟我们要找的key是否相等。

通俗的讲,hashMap的查找或者插入过程中我们使用hashCode来确定一个固定的桶,确定了桶范围之后,我们再使用equals方法在这个桶内,可能是链表或是红黑树中去找到真正对应的元素。

这也就是为什么需要这样的规范,如果hashCode和equals不稳定,那么我们在每次查找的时候,可能就到不同的桶里面去。如果两个对象equals但两个对象hashCode不相等的话,就导致我们在两次求这个index下标的时候求得不同的下标,可能就找不到对应的值。
在这里插入图片描述

final 在 java 中有什么作用?

https://www.cnblogs.com/tanshaoshenghao/p/10908771.html

final作为Java中的关键字可以用于三个地方。用于修饰类、类属性和类方法。

特征:凡是引用final关键字的地方不可修改。

  1. 修饰类:表示该类不能被继承
  2. 修饰方法:表示方法不能被重写
  3. 修饰变量:表示变量只能一次赋值以后值不能被修改(常量)

被final修饰的变量是不能够被改变的。但是这里的"不能够被改变"对于不同的数据类型是有不同的含义的。

当final修饰的是一个基本数据类型数据时,这个数据的值在初始化后将不能被改变

当final修饰的是一个引用类型数据时,也就是修饰一个对象时,引用在初始化后将永远指向一个内存地址,不可修改。但是该内存地址中保存的对象信息,是可以进行修改的。

final Person p = new Person(20, "ABCD");
p.setAge(18);   //可以修改p对象的数据
System.out.println(p.getAge()); //输出18
 
Person pp = new Person(30, "ABCD");
p = pp; //这行代码会报错, 不能通过编译, 因为p经final修饰永远指向上面定义的p对象, 不能指向pp对象.

final修饰变量的本质: final修饰的变量会指向一块固定的内存,这块内存中的值不能改变。

引用类型变量所指向的对象之所以可以修改,是因为引用变量不是直接指向对象的数据,而是指向对象的引用的。

所以被final修饰的引用类型变量将永远指向一个固定的对象,不能被修改;对象的数据值可以被修改。

被final修饰的常量在编译阶段会被放入常量池中

final是用于定义常量的,定义常量的好处是:不需要重复地创建相同的变量。而常量池是Java的一项重要技术,由final修饰的变量会在编译阶段放入到调用类的常量池中

    int n1 = 2019;          //普通变量
    final int n2 = 2019;    //final修饰的变量

    String s = "20190522";  
    String s1 = n1 + "0522";	//拼接字符串"20190512"
    String s2 = n2 + "0522";	

    System.out.println(s == s1);	//false
    System.out.println(s == s2);	//true
  • 上面的代码运作过程是这样的:
  • 首先根据final修饰的常量会在编译期放到常量池的原则, n2会在编译期间放到常量池中.
  • 然后s变量所对应的"20190522"字符串会放入到字符串常量池中, 并对外提供一个引用返回给s变量.
  • 这时候拼接字符串s1, 由于n1对应的数据没有放入常量池中, 所以s1暂时无法拼接, 需要等程序加载运行时才能确定s1对应的值.
  • 但在拼接s2的时候, 由于n2已经存在于常量池, 所以可以直接与"0522"拼接, 拼接出的结果是"20190522". 这时系统会查看字符串常量池, 发现已经存在字符串20190522, 所以直接返回20190522的引用. 所以s2和s指向的是同一个引用, 这个引用指向的是字符串常量池中的20190522.

当程序执行时。n1变量才有具体的指向。

当拼接s1的时候,会创建一个新的String类型对象,也就是说字符串常量池中的20190522会对外提供一个新的引用。

所以当s1与s用"=="判断时,由于对应的引用不同,会返回false。而s2和s指向同一个引用,返回true。

这个例子想说明的是: 由于被final修饰的常量会在编译期进入常量池,如果有涉及到该常量的操作,很有可能在编译期就已经完成。

final修饰方法

使用final修饰方法有两个作用,首要作用是锁定方法,不让任何继承类对其进行修改。

另外一个作用是在编译器对方法进行内联, 提升效率。但是现在已经很少这么使用了,近代的Java版本已经把这部分的优化处理得很好了。但是还是了解一下什么是方法内敛。

方法内敛:当调用一个方法时,系统需要进行保存现场信息,建立栈帧,恢复线程等操作,这些操作都是相对比较耗时的。如果使用final修饰一个了一个方法a,在其他调用方法a的类进行编译时,方法a的代码会直接嵌入到调用a的代码块中。

//原代码
public static void test(){
    String s1 = "包夹方法a";
    a();
    String s2 = "包夹方法a";
}

public static final void a(){
    System.out.println("我是方法a中的代码");
    System.out.println("我是方法a中的代码");
}

//经过编译后
public static void test(){
    String s1 = "包夹方法a";
    System.out.println("我是方法a中的代码");
    System.out.println("我是方法a中的代码");
    String s2 = "包夹方法a";
}

在方法非常庞大的时候,这样的内嵌手段是几乎看不到任何性能上的提升的,在最近的Java版本中,不需要使用final方法进行这些优化了。

final修饰类

使用final修饰类的目的简单明确:表明这个类不能被继承

当程序中有永远不会被继承的类时,可以使用final关键字修饰

被final修饰的类所有成员方法都将被隐式修饰为final方法

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

String不是基本的数据类型,是final修饰的java类,java中的基本类型一共有8个,它们分别为:

  1. 字符类型:byte,char
  2. 基本整型:short,int,long
  3. 浮点型:float,double
  4. 布尔类型:boolean

但是为什么会有这么一问呢?

  • 当我们在给一个String类型的变量赋值的时候我们是可以不使用 “new” 关键字的, 例如: String name = “小明”; 这样就ok了
  • 这种写法和基本数据类型的使用是很像的
  • java中的设计,String类型的值是存储在常量池中的。String类型是我们在编程中经常使用的数据类型。因此java的设计者将String类型做了一定的特殊处理。

测试代码:

String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");

System.out.println(s1.hashCode()); // 96354
System.out.println(s2.hashCode()); // 96354
System.out.println(s3.hashCode()); // 96354
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s3)); //true

可以知道

  • s1s2s3最终存储的都是同一个对象,因为他们的hashCode码是相同的。
  • s1s2不但最终存储的对象是相同的,而且直接指向的对象地址也是相同的,因为使用==返回的是true。
  • s1s3虽然最终存储的对象是同一个对象,但是他们的地址是不同的,因为打印出来的hashCode码相同但是使用==返回的是false。

常量池

https://blog.csdn.net/qq_41376740/article/details/80338158
https://www.cnblogs.com/syp172654682/p/8082625.html

Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。

所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。这种常量池主要用于存放两大类常量:字面量和符号引用量,字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

  • 类和接口的全限定名
  • 字段名称和描述符
  • 方法名称和描述符

而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。

String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。

常量池的好处

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。

例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。

  1. 节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
  2. 节省运行时间:比较字符串时,==equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。
    String s1 = "Hello";
    String s2 = "Hello";
    String s3 = "Hel" + "lo";
    String s4 = "Hel" + new String("lo");
    String s5 = new String("Hello");
    String s6 = s5.intern();
    String s7 = "H";
    String s8 = "ello";
    String s9 = s7 + s8;

    System.out.println(s1 == s2);  // true
    System.out.println(s1 == s3);  // true
    System.out.println(s1 == s4);  // false
    System.out.println(s1 == s9);  // false
    System.out.println(s4 == s5);  // false
    System.out.println(s1 == s6);  // true

分析:
1、s1 == s2 很容易可以判断出来。s1 和 s2 都指向了方法区常量池中的Hello。
2、s1 == s3 这里要注意一下,因为做+号的时候,会进行优化,自动生成Hello赋值给s3,所以也是true
3、s1 == s4 s4是分别用了常量池中的字符串和存放对象的堆中的字符串,做+的时候会进行动态调用,最后生成的仍然是一个String对象存放在堆中。
4、s1 == s9 在JAVA9中,因为用的是动态调用,所以返回的是一个新的String对象。所以s9和s4,s5这三者都不是指向同一块内存。
5、s1 == s6 为啥s1 和 s6地址相等呢? 归功于intern方法,这个方法首先在常量池中查找是否存在一份equal相等的字符串如果有的话就返回该字符串的引用,没有的话就将它加入到字符串常量池中,所以存在于class中的常量池并非固定不变的,可以用intern方法加入新的。

如何将字符串反转?

https://www.cnblogs.com/binye-typing/p/9260994.html

利用 StringBuffer 或 StringBuilder 的 reverse 成员方法:

	// StringBuffer
	public static String reverse1(String str){
        return new StringBuilder(str).reverse().toString();
    }

利用 String 的 toCharArray 方法先将字符串转化为 char 类型数组,然后将各个字符进行重新拼接:

	// toCharArray
	public static String reverse2(String str) {
		char[] chars = str.toCharArray();
		String reverse = "";
		for (int i = chars.length - 1; i >= 0; i--) {
			reverse += chars[i];
		}
		return reverse;
	}

利用 String 的 CharAt 方法取出字符串中的各个字符:

	// charAt
	public static String reverse3(String str) {
		String reverse = "";
		int length = str.length();
		for (int i = 0; i < length; i++) {
			reverse = str.charAt(i) + reverse;
		}
		return reverse;
	}

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

  • length() :得到一个字符串的字符个数
  • isEmpty() :判断字符串的长度是否为空
  • charAt :得到指定下标位置对应的字符
  • toCharArray() :将一个字符串转换成字符数组
  • split(String) :将一个字符串按照指定内容劈开
  • equals() :判断两个字符串的内容是否一样
  • equalsIsIgnoreCase(String) :忽略大小写的比较两个字符串的内容是否一样
  • contains(String) :判断一个字符串里面是否包含指定的内容
  • startsWith(String) :判断一个字符串是否以指定的内容开头
  • endsWith(String) :判断一个字符串是否以指定的内容结尾
  • toUpperCase() :将一个字符串全部转换成大写
  • toLowerCase() :将一个字符串全部转换成小写
  • replace(String,String) :将某个内容全部替换成指定内容
  • replaceAll(String,String) :将某个内容全部替换成指定内容,支持正则
  • repalceFirst(String,String) :将第一次出现的某个内容替换成指定的内容
  • substring(int) :从指定下标开始一直截取到字符串的最后
  • substring(int,int) :从下标x截取到下标y-1对应的元素
  • trim() :去除一个字符串的前后空格
  • indexOf(String) :得到指定内容第一次出现的下标
  • lastIndexOf(String) :得到指定内容最后一次出现的下标

Array和ArrayList有什么区别?

  • Array类型的变量在声明的同时必须进行实例化(至少得初始化数组的大小)
  • ArrayList可以只是先声明。
int[] array = new array[3];
int[] array = {1,2,3};
// 而直接使用 int[] array; 是不行的。

ArrayList myList = new ArrayList();

Array只能存储同构的对象,而ArrayList可以存储异构的对象。

  • 同构的对象是指类型相同的对象,若声明为 int[] 的数组就只能存放整形数据,string[] 只能存放字符型数据,但声明为 object[] 的数组除外。
  • ArrayList可以存放任何不同类型的数据。

Array是始终是连续存放的,而ArrayList的存放不一定连续。

Array对象的初始化必须只定指定大小,且创建后的数组大小是固定的。ArrayList的大小可以动态指定,其大小可以在初始化时指定,也可以不指定,也就是说该对象的空间可以任意增加。

Array和ArrayList的相似点

都具有索引(index),即可以通过index来直接获取和修改任意项。

他们所创建的对象都放在托管堆中。

都能够对自身进行枚举(因为都实现了IEnumerable接口)。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值