一、基础概念 1. Java 和 C++的区别?文章内容来源于对guide哥的java总结学习,兄弟们可以去guide哥的仓库地址 去看看 https://gitee.com/SnailClimb/JavaGuide ,多多支持guide哥,真的是一个非常棒的java知识领域的大佬
- 都是面向对象的语言,都支持封装、继承和多态
- java不提供指针访问内存,程序内存更安全
- java的类是单继承,c++的类是多继承,但是java的接口可以多继承
- java有自动内存管理垃圾回收机制GC,不需要手动释放内存
- c++支持方法重载和 *** 作符重载,而java只支持方法重载( *** 作符重载增加了复杂性,这与 Java 最初的设计思想不符)
形式 : 字符常量是单引号引起的一个字符,字符串常量是双引号引起的 0 个或若干个字符
含义 : 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
占内存大小 : 字符常量只占 2 个字节; 字符串常量占若干个字节 (注意: char 在 Java 中占两个字节),
2.continue、break 和 return 的区别是什么?在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词:
- continue :指跳出当前的这一次循环,继续下一次循环。
- break :指跳出整个循环体,继续执行循环下面的语句。
return 用于跳出所在方法,结束该方法的运行。return 一般有两种用法:
- return; :直接使用 return 结束方法执行,用于没有返回值函数的方法
- return value; :return 一个特定值,用于有返回值函数的方法
这个需要结合 JVM 的相关知识,主要原因如下:
- 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
- 在类的非静态成员不存在的时候静态成员就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法 *** 作。
静态方法和实例方法有何不同?
在外部调用静态方法时,可以使用 类名.方法名 的方式,也可以使用 对象.方法名 的方式,而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象 。
不过,需要注意的是一般不建议使用 对象.方法名 的方式来调用静态方法。这种方式非常容易造成混淆,静态方法不属于类的某个对象而是属于这个类。
因此,一般建议使用 类名.方法名 的方式来调用静态方法。
访问类成员是否存在限制
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制。
4.重载和重写的区别重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理
重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法
重载
发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同
重写
重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。
- 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
- 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
- 构造方法无法被重写
综上:重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。
⭐️ 关于 重写的返回值类型 这里需要额外多说明一下,上面的表述不太清晰准确:如果方法的返回类型是 void 和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。
public class Hero { public String name() { return "超级英雄"; } } public class SuperMan extends Hero{ @Override public String name() { return "超人"; } public Hero hero() { return new Hero(); } } public class SuperSuperMan extends SuperMan { public String name() { return "超级超级英雄"; } @Override public SuperMan hero() { return new SuperMan(); } }5. 泛型 1.Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符?
Java 泛型(generics) 是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所 *** 作的数据类型被指定为一个参数。
Java 的泛型是伪泛型,这是因为 Java 在运行期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除
泛型一般有三种使用方式: 泛型类、泛型接口、泛型方法。
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 //在实例化泛型类时,必须指定T的具体类型 public class Generic{ private T key; public Generic(T key) { this.key = key; } public T getKey() { return key; } }
如何实例化泛型类:
GenericgenericInteger = new Generic (123456);
2.泛型接口 :
public interface Generator{ public T method(); }
实现泛型接口,不指定类型:
class GeneratorImplimplements Generator { @Override public T method() { return null; } }
实现泛型接口,指定类型:
class GeneratorImpl implements Generator{ @Override public String method() { return "hello"; } }
3.泛型方法 :
public staticvoid printArray(E[] inputArray) { for (E element : inputArray) { System.out.printf("%s ", element); } System.out.println(); }
使用:
// 创建不同类型数组: Integer, Double 和 Character Integer[] intArray = { 1, 2, 3 }; String[] stringArray = { "Hello", "World" }; printArray(intArray); printArray(stringArray);2.常用的通配符有哪些?
常用的通配符为: T,E,K,V,?
- ? 表示不确定的 Java 类型
- T (type) 表示具体的一个 Java 类型
- K V (key value) 分别代表 Java 键值中的 Key Value
- E (element) 代表 Element
- 可用于定义通用返回结果 CommonResult
通过参数 T 可根据具体的返回类型动态指定结果的数据类型 - 定义 Excel 处理类 ExcelUtil
用于动态指定 Excel 导出的数据类型 - 用于构建集合工具类。参考 Collections 中的 sort, binarySearch 方法
- …
== 对于基本类型和引用类型的作用效果是不同的:
- 对于基本数据类型来说,== 比较的是值。
- 对于引用数据类型来说,== 比较的是对象的内存地址。
equals() 作用不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类。
Object 类 equals() 方法:
public boolean equals(Object obj) { return (this == obj); }
equals() 方法存在两种使用情况:
- 类没有覆盖 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。
- 类覆盖了 equals()方法 :一般我们都覆盖 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
举个例子(这里只是为了举例。实际上,你按照下面这种写法的话,像 IDEA 这种比较智能的 IDE 都会提示你将 == 换成 equals() ):
String a = new String("ab"); // a 为一个引用 String b = new String("ab"); // b为另一个引用,对象的内容一样 String aa = "ab"; // 放在常量池中 String bb = "ab"; // 从常量池中查找 System.out.println(aa == bb);// true System.out.println(a == b);// false System.out.println(a.equals(b));// true System.out.println(42 == 42.0);// true
String 中的 equals 方法是被重写过的,因为 Object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
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; }7.你重写过 hashCode() 和 equals()吗?为什么重写 equals() 时必须重写 hashCode() 方法? 1.hashCode() 有什么用?
hashCode() 的作用是获取哈希码(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。
其实, hashCode() 和 equals()都是用于比较两个对象是否相等。
2.那为什么 JDK 还要同时提供这两个方法呢?这是因为在一些容器(比如 HashMap、HashSet)中,有了 hashCode() 之后,判断元素是否在对应容器中的效率会更高(参考添加元素进HastSet的过程)!
我们在前面也提到了添加元素进HastSet的过程,如果 HashSet 在对比的时候,同样的 hashCode 有多个对象,它会继续使用 equals() 来判断是否真的相同。也就是说 hashCode 帮助我们大大缩小了查找成本。
3.那为什么不只提供 hashCode() 方法呢?这是因为两个对象的hashCode 值相等并不代表两个对象就相等。
4.那为什么两个对象有相同的 hashCode 值,它们也不一定是相等的?因为 hashCode() 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓哈希碰撞也就是指的是不同的对象得到相同的 hashCode )。
总结下来就是 :
- 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
- 如果两个对象的hashCode 值相等并且equals()方法返回 true,我们才认为这两个对象相等。
- 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。
因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。
总结 :
- equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
- 两个对象有相同的 hashCode 值,他们也不一定是相等的(哈希碰撞)。
Java 中有 8 种基本数据类型,分别为:
- 6 种数字类型 :byte、short、int、long、float、double
- 1 种字符类型:char
- 1 种布尔型:boolean。
这 8 种基本数据类型的默认值以及所占空间的大小如下:
另外,对于 boolean,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。
这八种基本类型都有对应的包装类分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean 。
包装类型不赋值就是 Null ,而基本类型有默认值且不是 Null。
基本数据类型直接存放在 Java 虚拟机栈中的局部变量表中,而包装类型属于对象类型,我们知道对象实例都存在于堆中。相比于对象类型, 基本数据类型占用的空间非常小。
四、java面向对象 1.成员变量与局部变量的区别有哪些?1.语法上:成员变量属于类,而局部变量是在代码块或方法中定义的变量或是方法的参数,成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
2.内存上:如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
3.生存周期:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
4.默认值:成员变量如果没有被赋初,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
2. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的? 1.String不可变的原因?- 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
- String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。
3.线程安全性String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本 *** 作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
4.性能每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行 *** 作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
- *** 作少量的数据: 适用 String
- 单线程 *** 作字符串缓冲区下 *** 作大量数据: 适用 StringBuilder
- 多线程 *** 作字符串缓冲区下 *** 作大量数据: 适用 StringBuffer
浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
五、反射 1.何为反射?如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。
反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。
通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
2.反射机制优缺点- 优点 : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
- 缺点 :让我们在运行时有了分析 *** 作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。Java Reflection: Why is it so slow? (opens new window)
但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。
六、异常 1.try-catch-finallytry块: 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
catch块: 用于处理 try 捕获到的异常。
finally 块: 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。
在以下 3 种特殊情况下,finally 块不会被执行:
- 在 try 或 finally块中用了 System.exit(int)退出程序。但是,如果 System.exit(int) 在异常语句之后,finally 还是会被执行
- 程序所在的线程死亡。
- 关闭 CPU。
注意: 当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。如下:
public class Test { public static int f(int value) { try { return value * value; } finally { if (value == 2) { return 0; } } } }
如果调用 f(2),返回值将是 0,因为 finally 语句的返回值覆盖了 try 语句块的返回值
2.使用 try-with-resources 来代替try-catch-finally适用范围(资源的定义): 任何实现 java.lang.AutoCloseable或者 java.io.Closeable 的对象
关闭资源和 finally 块的执行顺序: 在 try-with-resources 语句中,任何 catch 或 finally 块在声明的资源关闭后运行
七、I/O流 1.什么是序列化?什么是反序列化?如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
简单来说:
- 序列化: 将数据结构或对象转换成二进制字节流的过程
- 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
综上:序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
2.Java 序列化中如果有些字段不想进行序列化,怎么办?对于不想进行序列化的变量,使用 transient 关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。
关于 transient 还有几点注意:
- transient 只能修饰变量,不能修饰类和方法。
- transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0。
- static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化。
方法 1:通过 Scanner
Scanner input = new Scanner(System.in); String s = input.nextLine(); input.close();
方法 2:通过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); String s = input.readLine();4.Java 中 IO 流分为几种?
- 按照流的流向分,可以分为输入流和输出流;
- 按照 *** 作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流。
Java IO 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
按 *** 作方式分类结构图
按 *** 作对象分类结构图:
5.既然有了字节流,为什么还要有字符流?回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接 *** 作字符的接口,方便我们平时对字符进行流 *** 作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
有规则,而且彼此之间存在非常紧密的联系, Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)