网站建设与网站制作百度电脑版网页版
- 作者: 五速梦信息网
- 时间: 2026年04月20日 07:42
当前位置:
首页
>
news
>正文
网站建设与网站制作,百度电脑版网页版,网站策划步骤,地理云门户网站建设变量
成员变量与局部变量的区别 语法形式#xff1a;从语法形式上看#xff0c;成员变量是属于类的#xff0c;而局部变量是在代码块或方法中定义的变量或是方法的参数#xff1b;成员变量可以被 public,private,static 等修饰符所修饰#xff0c;而局部变量不能被访问控…变量
成员变量与局部变量的区别 语法形式从语法形式上看成员变量是属于类的而局部变量是在代码块或方法中定义的变量或是方法的参数成员变量可以被 public,private,static 等修饰符所修饰而局部变量不能被访问控制修饰符及 static 所修饰但是成员变量和局部变量都能被 final 所修饰。 存储方式从变量在内存中的存储方式来看如果成员变量是使用 static 修饰的那么这个成员变量是属于类的如果没有使用 static 修饰这个成员变量是属于实例的。而对象存在于堆内存局部变量则存在于栈内存。 生存时间从变量在内存中的生存时间上看成员变量是对象的一部分它随着对象的创建而存在而局部变量随着方法的调用而自动生成随着方法的调用结束而消亡。 默认值从变量是否有默认值来看成员变量如果没有被赋初始值则会自动以类型的默认值而赋值一种情况例外:被 final 修饰的成员变量也必须显式地赋值而局部变量则不会自动赋值。
那为什么成员变量要有默认值呢 因为如果不给成员变量默认值而是指向随机内存中的一块区域在读取该值的时候就会报错 默认值有两种设置方式手动和自动根据第一点没有手动赋值一定要自动赋值。成员变量在运行时可借助反射等方法手动赋值而局部变量不行 对于编译器javac来说局部变量没赋值很好判断可以直接报错。而成员变量可能是运行时赋值无法判断误报“没默认值”又会影响用户体验所以采用自动赋默认值
静态变量又有什么作用呢
所谓静态变量就是被static关键字修饰的变量这个变量被类的所有实例所共享无论有多少实例它们共享的都是同一份静态变量在JVM运行时只为静态变量分配一次内存这样一来就算实例很多也不用重复创建节省内存
静态变量可以通过 类名.变量名的方式直接访问(此变量被private访问修饰符修饰就不行)
通常情况下静态变量会被final修饰成常量使用
public class ConstantVariableExample {// 常量public static final int constantVar 0;
}
字符型常量和字符串常量的区别? 形式 : 字符常量是单引号引起的一个字符字符串常量是双引号引起的 0 个或若干个字符。 含义 : 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)。 占内存大小字符常量只占 2 个字节; 字符串常量占若干个字节。 注意 char 在 Java 中占两个字节 方法
静态方法为什么不能调用非静态成员?
结合JVM知识来说静态方法是属于类的在类加载时分配内存就存在了而非静态的成员是依赖于类的实例的用一个早就存在的方法去调用一个目前在内存中不存在的成员这种操作是非法的也调用不了
静态方法和实例方法有何不同
1.调用方式
静态方法的调用方式有两种可以通过类名.方法名的方式调用也可以通过创建类实例使用对象名.方法名的方式调用而实例方法只能通过后者的方式调用
但是一般情况下都用类名.方法名的方式调用因为用第二种方式的话容易混淆
2.类成员的访问方式
静态方法只能访问静态类成员静态变量、静态方法实例成员无法访问实例变量、实例方法而实例方法不受限制
重载
发生在同一个类中或者父类和子类之间方法名必须相同参数类型不同、个数不同、顺序不同方法返回值和访问修饰符可以不同
编译器必须挑选出具体执行哪个方法它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。 如果编译器找不到匹配的参数 就会产生编译时错误 因为根本不存在匹配 或者没有一个比其他的更好(这个过程被称为重载解析(overloading resolution)) Java 允许重载任何方法 而不只是构造器方法。 重写
重写发生在运行期是子类对父类的允许访问的方法的实现过程进行重新编写。 方法名、参数列表必须相同子类方法返回值类型应比父类方法返回值类型更小或相等抛出的异常范围小于等于父类访问修饰符范围大于等于父类。 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法但是被 static 修饰的方法能够被再次声明。 构造方法无法被重写
重载和重写有什么区别 重载就是同样的一个方法能够根据输入数据的不同做出不同的处理 重写就是当子类继承自父类的相同方法输入数据一样但要做出有别于父类的响应时你就要覆盖父类方法 区别点重载方法重写方法发生范围同一个类子类参数列表必须修改一定不能修改返回类型可修改子类方法返回值类型应比父类方法返回值类型更小或相等异常可修改子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等访问修饰符可修改一定不能做更严格的限制可以降低限制发生阶段编译期运行期
可变长参数
从 Java5 开始Java 支持定义可变长参数所谓可变长参数就是允许在调用方法时传入不定长度的参数。就比如下面这个方法就可以接受 0 个或者多个参数
可变参数只能作为函数的最后一个参数但其前面可以有也可以没有任何其他参数
public static void method2(String arg1, String… args) {//……
}
遇到方法重载的情况怎么办呢会优先匹配固定参数还是可变参数的方法呢
答案是会优先匹配固定参数的方法因为固定参数的方法匹配度更高
public class VariableLengthArgument {
public static void printVariable(String… args) {for (String s : args) {System.out.println(s);}}
public static void printVariable(String arg1, String arg2) {System.out.println(arg1 arg2);}
public static void main(String[] args) {printVariable(a, b);printVariable(a, b, c, d);}
}
输出
ab
a
b
c
d Java 的可变参数编译后实际会被转换成一个数组 面向对象
面向对象和面向过程的区别
面向过程编程Procedural-Oriented ProgrammingPOP和面向对象编程Object-Oriented ProgrammingOOP是两种常见的编程范式两者的主要区别在于解决问题的方式不同 面向过程编程POP面向过程把解决问题的过程拆成一个个方法通过一个个方法的执行解决问题。 面向对象编程OOP面向对象会先抽象出对象然后用对象执行方法的方式解决问题。
相比较于 POPOOP 开发的程序一般具有下面这些优点 易维护由于良好的结构和封装性OOP 程序通常更容易维护。 易复用通过继承和多态OOP 设计使得代码更具复用性方便扩展功能。 易扩展模块化设计使得系统扩展变得更加容易和灵活。
POP 的编程方式通常更为简单和直接适合处理一些较简单的任务 POP 和 OOP 的性能差异主要取决于它们的运行机制而不仅仅是编程范式本身如果只是简单地去比较两者的性能差异是一种误区面向过程也需要分配内存、计算内存偏移量Java性能较差的原因并不只是因为它是面向对象这么简单而是因为Java本身是一种半编译语言最终的执行代码并不是可以直接被CPU执行的二进制机械码而面向过程的语言一般都是直接编译成二进制的机械码一些面向过程的脚本语言性能也不见得比Java好 在选择编程范式时性能并不是唯一的考虑因素。代码的可维护性、可扩展性和开发效率同样重要。
现代编程语言基本都支持多种编程范式既可以用来进行面向过程编程也可以进行面向对象编程。
创建一个对象用什么运算符?对象实体与对象引用有何不同
创建对象通常使用new关键字new关键字创建对象实例对象实例存放在堆内存中而对象的引用指向对象的实例对象引用存放在栈内存中 一个对象引用可以指向 0 个或 1 个对象一对一 一个对象可以有 n 个引用指向它一对多
面向对象三大特征
封装
封装是指把一个对象的状态信息也就是属性隐藏在对象内部不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息也就是属性但是可以通过遥控器方法来控制空调。如果属性不想被外界访问我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法那么这个类也没有什么意义了。就好像如果没有空调遥控器那么我们就无法操控空凋制冷空调本身就没有意义了当然现在还有很多其他方法 这里只是为了举例子
继承
不同类型的对象相互之间经常有一定数量的共同点。例如张三、李四、王五都共享人的特性体重、身高等。同时每一个对象还定义了额外的特性使得他们与众不同。例如张三比较重李四又比较轻王五又比较高。继承是使用已存在的类的定义作为基础建立新类的技术新类的定义可以增加新的数据或新的功能也可以用父类的功能但不能选择性地继承父类。通过使用继承可以快速地创建新的类可以提高代码的重用程序的可维护性节省大量创建新类的时间 提高我们的开发效率 注意 子类拥有父类对象所有的属性和方法包括私有属性和私有方法但是父类中的私有属性和方法子类是无法访问只是拥有。 子类可以拥有自己属性和方法即子类可以对父类进行扩展。 子类可以用自己的方式实现父类的方法。 多态
多态顾名思义表示一个对象具有多种的状态具体表现为父类的引用指向子类的实例。 对象类型和引用类型之间具有继承类/实现接口的关系 引用类型变量发出的方法调用的到底是哪个类中的方法必须在程序运行期间才能确定 多态不能调用“只在子类存在但在父类不存在”的方法 如果子类重写了父类的方法真正执行的是子类重写的方法如果子类没有重写父类的方法执行的是父类的方法。
深拷贝和浅拷贝区别了解吗什么是引用拷贝 浅拷贝浅拷贝会在堆上创建一个新的对象区别于引用拷贝的一点不过如果原对象内部的属性是引用类型的话浅拷贝会直接复制内部对象的引用地址也就是说拷贝对象和原对象共用同一个内部对象。 深拷贝深拷贝会完全复制整个对象包括这个对象所包含的内部对象。
那什么是引用拷贝呢 简单来说引用拷贝就是两个不同的引用指向同一个对象
浅拷贝、深拷贝、引用拷贝的原理 Object
Object 类的常见方法
/*** native 方法用于返回当前运行时对象的 Class 对象使用了 final 关键字修饰故不允许子类重写。/
public final native Class? getClass()
/** native 方法用于返回对象的哈希码主要使用在哈希表中比如 JDK 中的HashMap。/
public native int hashCode()
/** 用于比较 2 个对象的内存地址是否相等String 类对该方法进行了重写以用于比较字符串的值是否相等。/
public boolean equals(Object obj)
/** native 方法用于创建并返回当前对象的一份拷贝。/
protected native Object clone() throws CloneNotSupportedException
/** 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。/
public String toString()
/** native 方法并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。/
public final native void notify()
/** native 方法并且不能重写。跟 notify 一样唯一的区别就是会唤醒在此对象监视器上等待的所有线程而不是一个线程。/
public final native void notifyAll()
/** native方法并且不能重写。暂停线程的执行。注意sleep 方法没有释放锁而 wait 方法释放了锁 timeout 是等待时间。/
public final native void wait(long timeout) throws InterruptedException
/** 多了 nanos 参数这个参数表示额外时间以纳秒为单位范围是 0-999999。 所以超时的时间还需要加上 nanos 纳秒。。/
public final void wait(long timeout, int nanos) throws InterruptedException
/** 跟之前的2个wait方法一样只不过该方法一直等待没有超时时间这个概念/
public final void wait() throws InterruptedException
/** 实例被垃圾回收器回收的时候触发的操作*/
protected void finalize() throws Throwable { }
hashCode() 有什么用
hashCode() 的作用是获取哈希码int 整数也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置 hashCode() 定义在 JDK 的 Object 类中这就意味着 Java 中的任何类都包含有 hashCode() 函数。另外需要注意的是Object 的 hashCode() 方法是本地方法也就是用 C 语言或 C 实现的 该方法在 Oracle OpenJDK8 中默认是 使用线程局部状态来实现 Marsaglias xor-shift 随机数生成, 并不是 地址 或者 地址转换而来, 不同 JDK/VM 可能不同在 Oracle OpenJDK8 中有六种生成方式 (其中第五种是返回地址), 通过添加 VM 参数: -XX:hashCode4 启用第五种。参考源码: jdk8u/jdk8u/hotspot: 87ee5ee27509 src/share/vm/runtime/globals.hpp1127 行 jdk8u/jdk8u/hotspot: 87ee5ee27509 src/share/vm/runtime/synchronizer.cpp537 行开始 散列表存储的是键值对(key-value)它的特点是能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码可以快速找到所需要的对象
那为什么要有 hashCode呢
equals方法中就重写了hashCode方法我们在比较两个对象是否相等时通过哈希算法计算出他们的哈希值看是否相等然而这只是一个前提条件哈希值相同的两个对象不一定相等因为有哈希碰撞这一情况即可能有多个对象传回的Hash值相同越糟糕的哈希算法越容易碰撞但这也与数据值域分布的特性有关所谓哈希碰撞也就是指的是不同的对象得到相同的 hashCode )这也是为什么JDK要提供equals和hashCode这两个方法的原因并且在一些容器中HashSet、HashMap使用hashCode能大大缩小查找的成本判断元素是否在对应容器中的效率会更高 当你把对象加入 HashSet 时HashSet 会先计算对象的 hashCode 值来判断对象加入的位置同时也会与其他已经加入的对象的 hashCode 值作比较如果没有相符的 hashCodeHashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同HashSet 就不会让其加入操作成功。如果不同的话就会重新散列到其他位置。这样我们就大大减少了 equals 的次数相应就大大提高了执行速度 总结下来就是 如果两个对象的hashCode 值相等那这两个对象不一定相等哈希碰撞。 如果两个对象的hashCode 值相等并且equals()方法也返回 true我们才认为这两个对象相等。 如果两个对象的hashCode 值不相等我们就可以直接认为这两个对象不相等
String
String、StringBuffer、StringBuild之间的区别
可变性
String线程安全且不可变的
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类在 AbstractStringBuilder 中也是使用字符数组保存字符串不过没有使用 final 和 private 关键字修饰最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法
线程安全性
String 中的对象是不可变的也就可以理解为常量线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类定义了一些字符串的基本操作如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁所以是线程安全的。StringBuilder 并没有对方法进行加同步锁所以是非线程安全的。
性能
每次对 String 类型进行改变的时候都会生成一个新的 String 对象然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升但却要冒多线程不安全的风险。
对于三者使用的总结 操作少量的数据: 适用 String 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
String 为什么是不可变的?
String 类中使用 final 关键字修饰字符数组来保存字符串我们知道被 final 关键字修饰的类不能被继承修饰的方法不能被重写修饰的变量是基本数据类型则值不能改变修饰的变量是引用类型则不能再指向其他对象。因此final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因因为这个数组保存的字符串是可变的final 修饰引用类型变量的情况
String 真正不可变有下面几点原因 保存字符串的数组被 final 修饰且为私有的并且String 类没有提供/暴露修改这个字符串的方法。 String 类被 final 修饰导致其不能被继承进而避免了子类破坏 String 不可变。 在 Java 9 之后String、StringBuilder 与 StringBuffer 的实现改用 byte 数组存储字符串新版的 String 其实支持两个编码方案Latin-1 和 UTF-16。如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下byte 占一个字节(8 位)char 占用 2 个字节16byte 相较 char 节省一半的内存空间 JDK 官方就说了绝大部分字符串对象只包含 Latin-1 可表示的字符 字符串拼接用“” 还是 StringBuilder?
其实Java 语言本身并不支持运算符重载“”和“”是专门为 String 类重载过的运算符也是Java 中仅有的两个重载过的运算符
字符串对象通过“”的字符串拼接方式实际上是通过 StringBuilder 调用 append() 方法实现的拼接完成之后调用 toString() 得到一个 String 对象 。
不过在循环内使用“”进行字符串的拼接的话存在比较明显的缺陷编译器不会创建单个 StringBuilder 以复用会导致创建过多的 StringBuilder 对象
如果直接使用 StringBuilder 对象进行字符串拼接的话就不会存在这个问题了 在 JDK 9 中字符串相加“”改为用动态方法 makeConcatWithConstants() 来实现通过提前分配空间从而减少了部分临时对象的创建。然而这种优化主要针对简单的字符串拼接如 abc 。对于循环中的大量拼接操作仍然会逐个动态分配内存类似于两个两个 append 的概念并不如手动使用 StringBuilder 来进行拼接效率高。这个改进是 JDK9 的 JEP 280 提出的关于这部分改进的详细介绍推荐阅读这篇文章还在无脑用 StringBuilder来重温一下字符串拼接吧 以及参考 issue#2442。 String#equals() 和 Object#equals() 有何区别
String 中的 equals 方法是被重写过的比较的是 String 字符串的值是否相等。 Object 的 equals 方法是比较的对象的内存地址
字符串常量池的作用
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串String 类专门开辟的一块区域主要目的是为了避免字符串的重复创建
// 在字符串常量池中创建字符串对象 ”ab“
// 将字符串对象 ”ab“ 的引用赋值给 aa
String aa ab;
// 直接返回字符串常量池中字符串对象 ”ab“赋值给引用 bb
String bb ab;
System.out.println(aabb); // true
String#intern 方法
String.intern() 是一个 native (本地) 方法用来处理字符串常量池中的字符串对象引用。它的工作流程可以概括为以下两种情况 常量池中已有相同内容的字符串对象如果字符串常量池中已经有一个与调用 intern() 方法的字符串内容相同的 String 对象intern() 方法会直接返回常量池中该对象的引用 常量池中没有相同内容的字符串对象如果字符串常量池中还没有一个与调用 intern() 方法的字符串内容相同的对象intern() 方法会将当前字符串对象的引用添加到字符串常量池中并返回该引用
总结 intern() 方法的主要作用是确保字符串引用在常量池中的唯一性 当调用 intern() 时如果常量池中已经存在相同内容的字符串则返回常量池中已有对象的引用否则将该字符串添加到常量池并返回其引用
String 类型的变量和常量做“”运算时发生了什么
字符串不加 final 关键字拼接的情况JDK1.8
String str1 str;
String str2 ing;
String str3 str ing;
String str4 str1 str2;
String str5 string;
System.out.println(str3 str4);//false
System.out.println(str3 str5);//true
System.out.println(str4 str5);//false
对于编译期可以确定值的字符串也就是常量字符串 jvm 会将其存入字符串常量池。并且字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池这个得益于编译器的优化
在编译过程中Javac 编译器下文中统称为编译器会进行一个叫做 常量折叠(Constant Folding) 的代码优化,常量折叠会把常量表达式的值求出来作为常量嵌在最终生成的代码中这是 Javac 编译器会对源代码做的极少量优化措施之一(代码优化几乎都在即时编译器中进行)
对于 String str3 str ing; 编译器会给你优化成 String str3 string;
引用的值在程序编译期是无法确定的编译器无法对其进行优化。
对象引用和“”的字符串拼接方式实际上是通过 StringBuilder 调用 append() 方法实现的拼接完成之后调用 toString() 得到一个 String 对象
String str4 new StringBuilder().append(str1).append(str2).toString();
所以说我们在平时写代码的时候尽量避免多个字符串对象拼接因为这样会重新创建对象。如果需要改变字符串的话可以使用 StringBuilder 或者 StringBuffer
但是字符串使用 final 关键字声明之后可以让编译器当做常量来处理被 final 关键字修饰之后的 String 会被编译器当做常量来处理编译器在程序编译期就可以确定它的值其效果就相当于访问常量。如果 编译器在运行时才能知道其确切值的话就无法对其优化
final String str1 str;
final String str2 getStr();
String c str ing;// 常量池中的对象
String d str1 str2; // 在堆上创建的新的对象
System.out.println(c d);// false
public static String getStr() {return ing;
}