学习Java基础 常见问题
基础
JDK、JDK、JRE的关系
Java基本数据类型
final作用
final修饰 | 解释 |
---|---|
类 | 不可以被继承 |
方法 | 不能被重写 |
变量 | 不能被改变,不可变值的是变量的引用,指向的内容可以改变 |
final finally finalize
区别 | 描述 |
---|---|
final | 如上解释 |
finally | 一般作用在try-catch代码块中,一般用来存放一些关闭资源的代码 |
finalize | 属于Object类的一个方法,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。 |
static作用
static作用 | |
---|---|
1 | 变量或者方法是独立于该类的任何对象,被类的实例对象所共享。 |
2 | 该类被第一次加载的时候,就会去加载被static修饰的部分,但只有在类第一次使用时才进行初始化 |
3 | 在类加载的时候分配空间,以后创建类对象的时候不会重新分配 |
4 | 修饰的变量或者方法是优先于对象存在的 |
面向对象、面向过程
区别 | 优点 | 缺点 |
---|---|---|
面向对象 | 易维护、易复用、易扩展 | 性能比面向过程低 |
面向过程 | 能比面向对象高,因为类调用时需要实例化,开销比较大 | 没有面向对象易维护、易复用、易扩展 |
面向对象三大特征
面向对象三大特征 | 解释 |
---|---|
封装 | 隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性 |
继承 | 通过使用继承可以提高代码复用性。继承是多态的前提 |
多态 | 可以指向子类或具体实现类的实例对象。提高了程序的拓展性 |
String、StringBuffer、StringBuilder
String | StringBuffer | StringBuilder | |
---|---|---|---|
可变性 | 不可变 | 可变 | 可变 |
安全性 | 安全,因为final | 安全,因为加锁 | 不安全 |
适用 | 少量操作 | 多线程+大量操作 | 单线程+大量操作 |
Int和Integer的区别
// 问题1:Integer和int比较 Integer a = new Integer(3); Integer b = 3; System.out.println(a == b);// false,引用类型和值类型不能比较 Integer d = new Integer(3); System.out.println(a == d); // false,两个引用类型用==不能比较 int c = 3; System.out.println(c == d); // true,Integer遇到int比较,Integer会拆箱成int做值比较 System.out.println("-------");
Integer底层提前缓存好了[-128,127]的值,所以创建两个范围的对象时,地址是一样的
// 问题2:Integer值返回缓存 Integer f1 = 100; Integer f2 = 100; System.out.println(f1 == f2);// true Integer f3 = 129; Integer f4 = 129; System.out.println(f3 == f4); System.out.println("-------");// false
原因:当一个Integer对象赋给int值的使用,调用Integer的valueOf
方法
public static Integer valueOf(int i) { // i在[-128,127]时,就会自动去引用常量池中的Integer对象,不会new新的 if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
Equals、==、hashCode区别
== | equals | hashCode |
---|---|---|
基本数据类型用,比较的是首地址值 | 引用类型用,比较是内容值 | 对象在内存中的地址,算出对象的哈希码值,并将其返回,重写equals方法,必须重写hashCode,因为集合类是通过HashCode判断重复的 |
序列化类中有一个不可序列化的对象
给该类设置关键字transiient
告诉JDK不可被序列化
元注解
Java中使用返回值类型@interface表示该类是一个注解配置类,注解配置不能使用class、interface、abstract修饰
// 自定义注解,以下只是简单的演示。实际开发注解还需要一个注解处理器,自行百度学习 @Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) @Documented public @interface FruitProvider { public int id() default -1; public String name() default ""; public String address() default ""; }
public class Apple { // 使用自定义注解 @FruitProvider(id = 1, name = "红富士", address = "北京市") private String appleProvider; public String getAppleProvider() { return appleProvider; } public void setAppleProvider(String appleProvider) { this.appleProvider = appleProvider; } }
四大元注解:目标、保留、文档、继承
@Target:说明注解对象修饰范围
@Retention:该注解保留时长
- source :在源文件中有效, 源文件中就保留
- class:字节码文件中有效, 即在Class文件中被保留
- runtime:在运行时有效,即在运行时被保留
@Documented:表示被Javadoc文档工具化,只是一个标记注解,没有成员
@Inherited:表示该类型是被继承的,表名该注解类可以作用于其子类
Java的面向对象
名称 | 概念 |
---|---|
封装 | 将事物封装成一个类,减少耦合,隐藏细节。保留特定接口和外部联系 |
继承 | 从已知的类中派生出一个新的类,可以通过覆盖/重写增强功能 |
多态 | 本质是一个程序中存在多个同名的不同方法 |
封装:将事物封装成一个类,减少耦合,隐藏细节。保留特定接口和外部联系
继承:从已知的类中派生出一个新的类,可以通过覆盖/重写增强功能
- Java中类的初始化顺序:
- 父类静态成员变量、静态代码块;子类静态成员变量、静态代码块
- 父类普通成员变量和代码块,再执行父类构造方法
- 子类普通成员变量和代码块,再执行父类构造方法
- 子类特点:
- 父类非private的属性和方法
- 添加自己的方法和属性,对父类进行扩展
- 重新定义父类的方法=方法的覆盖/重写
多态:本质是一个程序中存在多个同名的不同方法
子类的覆盖实现
方法的重载实现
子类作为父类对象使用
什么方法重载、方法重写?
重载(overload):一个类中存在多个同名的不同方法,这些方法的参数个数、顺序和类型不同均可以构成方法重载
重写(override):子类对父类非私有方法的重新编写,涉及写的就会有子父类
如果只有方法返回值不同,可以构成重载吗?
不可以,因为我们调用某个方法,并不关心返回值。编译器根据方法名和参数无法确定我们调用的是那个方法。
Java中有goto关键字吗
goto和const是Java中的保留字,现在未使用,未使用的原因是保证程序的可读性,并且避免使用beak+lebel带标签的语句
跳出循环方式1 | 跳出循环方式2 | 跳出循环方式3 |
---|---|---|
break+label,不推荐使用 | flag+break | throw new 抛出异常 |
抽象类和接口的
相同点 | 不同点 |
---|---|
都不能直接实例化。 | 设计理念不同:抽象类是对对象/类的抽象,接口是对行为的抽象 |
都可以有默认的实现方法(JDK8以后才有的) | 抽象类只能单一继承,接口可以多重继承;抽象类可以有构造器,接口没有 |
都可以不需要实现类或者继承类去实现所有方法 | 抽象类中的抽象方法可以用public/protected/default abstract修饰;抽象类中的变量可以在子类中重新定义并赋值 |
如果要实例化,抽象类变量必须实现所有抽象方法,接口变量必须实现所有接口未实现的方法 | 接口的方法默认修饰符是public abstract,可以加statci关键字;接口中的变量默认是public static final,且必须给初值,在实现类中不能被重新定义和赋值 |
接口和抽象类该如何选择?
当我们仅仅只需要定义一些抽象方法而不需要额外的具体方法/变量的时候,用接口;反之,考虑抽象类
接口的普通方法、default修饰方法:
public interface MyInterface { // 接口的普通方法只能等待实现类实现,不能具体定义 void test(); // 但是JDK8以后,接口可以default声明,然后具体定义 default void say(String message) { System.out.println("hello:"+message); } }
public class MyInterfaceImpl implements MyInterface { // 实现接口里的抽象方法 @Override public void test() { System.out.println("test被执行"); } // 当然也可以重写say方法 public static void main(String[] args) { MyInterfaceImpl client = new MyInterfaceImpl(); client.test(); client.say("World"); } }
执行结果:
test被执行 hello:World
如果实现类实现了两个接口,两个接口都有相同的(default)默认方法名,那么该方法重写会报错
解决办法:
- 实现类重写多个多个接口的默认方法
- 手动指定重写哪个接口的默认方法
public interface MyInterface { void test(); default void say(String message) { System.out.println("hello:"+message); } } public interface MyInterface1 { default void say(String message) { System.out.println("hello1:" + message); } }
public class MyInterfaceImpl1 implements MyInterface, MyInterface1 { @Override public void test() { System.out.println("test是普通方法被重写"); } @Override public void say(String message) { // 方法一:System.out.println("实现类重写多个接口相同的默认方法:" + message); // 方法二:指定重写哪个接口的默认方法 MyInterface.super.say(message); } public static void main(String[] args) { MyInterfaceImpl1 client = new MyInterfaceImpl1(); client.say("好的"); } }
执行结果:
实现类重写多个接口相同的默认方法:hello+好的
浅拷贝和深拷贝
浅拷贝 | 深拷贝 |
---|---|
被复制的对象的所有变量都含有与原来对象相同的值,对拷贝后对象的引用依然指向原来的对象 | 不仅复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值 |
创建对象的方式
创建对象方式 | 是否调用构造器 |
---|---|
new + 类名 | 是 |
Class.newInstance | 是 |
Constructor.newInstance | 是 |
Clone | 否 |
反序列化 | 否 |
值传递和引用传递
值传递:传递是一个对象副本,即使副本改变,也不会影响原对象
引用传递:传递不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上
public class Test { public static void main(String[] args) { int a = 1; // 基本数据类型:值传递,原值不会变 change(a); System.out.println(a); } private static void change(int num) { num++; } }
public class Test1 { public static void main(String[] args) { // 以下2个是引用传递,会改变原值 StringBuilder hello1 = new StringBuilder("hello1"); StringBuffer hello2 = new StringBuffer("hello2"); // String存放在常量池,虽然是引用传递,但不会改变原值 String hello3 = new String("hello3"); change1(hello1); change2(hello2); change3(hello3); System.out.println(hello1); System.out.println(hello2); System.out.println(hello3); } private static void change3(String str) { str += " world"; } private static void change1(StringBuilder str) { str.append(" world"); } private static void change2(StringBuffer str) { str.append(" world"); } }
public class Test3 { public static void main(String[] args) { StringBuffer hello = new StringBuffer("hello"); System.out.println("before:" + hello); changeData(hello); // 前后值:都没有发生改变 // 因为changeData中str形参重新执行了str1,与原值hello无关了 System.out.println("after:" + hello); } private static void changeData(StringBuffer str) { StringBuffer str1 = new StringBuffer("Hi"); str = str1; str1.append("world"); } }
public class PassTest { 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); /* 结果:传值还是传引用? i = 1 传值。基本数据类型不会变 str = hello 传常量池地址。字符串不变 num = 200,传堆中的地址。原包装类不变,和字符串一样 arr = [2, 2, 3, 4, 5] 传堆中数组的首地址。发生了改变 my.a = 11,传堆中地址,资源类变量发生改变。资源类中的变量,会在堆中生成一个实例 */ System.out.println("i = " + i); System.out.println("str = " + str); System.out.println("num = " + num); System.out.println("arr = " + Arrays.toString(arr)); System.out.println("my.a = " + my.a); } public static void change(int j, String s, Integer num, int[] arr, MyData myData) { j += 1; s += "world"; num += 1; arr[0] += 1; myData.a += 1; } } class MyData { int a = 10; }
结果:
- 基本数据类型是值传递,不会改变原值
- String和包装类是引用传递,但不会改变原值,因为形参指向了另一个新生成的对象,原值不变
- 数组和自定义类时引用传递,会改变原值,因为数组是连续地址空间,没有在堆中新生成实例;自定义类中的成员变量分配在堆中,也没有重新生成实例
i = 1 // 传值。基本数据类型不会变 str = hello // 传常量池地址。字符串不变 num = 200 // 传堆中的地址。原包装类不变,和字符串一样 arr = [2, 2, 3, 4, 5]// 传堆中数组的首地址。发生了改变 my.a = 11// 传堆中地址,资源类变量发生改变。资源类中的变量,会在堆中生成一个实例
权限修饰符
作用域 | 当前类 | 同一包 | 子孙类 | 其他包 |
---|---|---|---|---|
public | 可以 | 可以 | 可以 | 可以 |
protected | 可以 | 可以 | 可以 | 不可以 |
default | 可以 | 可以 | 不可以 | 不可以 |
private | 可以 | 不可以 | 不可以 | 不可以 |
反射机制
反射优缺点
反射优点 | 反射缺点 | 反射使用场景 |
---|---|---|
装载到JVM中得的信息,动态获取类的属性方法等信息,提高语言灵活性和扩展性 | 性能较差,速度低于直接运行 | Spring等框架 |
提高代码复用率 | 程序的可维护性降低 | 动态代理 |
获取字节码
方式1 | 方式2 | 方式3 |
---|---|---|
类名.class | 对象名.getClass() | Class.forName(classPath) |
public class User { String username; String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
public class ThreeClassGetDemo { public static void main(String[] args) throws ClassNotFoundException { // 方式一:类.class Class<Integer> intClass = int.class; // 方式二:对象.getClass() User user = new User(); Class<? extends User> userClass = user.getClass(); // 方式三:Class.forName(类名) String ClassName = "基础.反射.User"; Class<?> userClass1 = Class.forName(ClassName); } }
public class UserClassDemo { public static void main(String[] args) { String className = "基础.反射.User"; try { // 通过反射获取userClass Class<?> userClassByForName = Class.forName(className); // 获取构造器 Constructor<?> constructor = userClassByForName.getConstructor(); // 生成user实例 User user = (User) constructor.newInstance(); user.setUsername("张三"); user.setPassword("123"); System.out.println(user); // 反射来修改成员变量 Class<? extends User> userClassByuser = user.getClass(); userClassByuser.getDeclaredField("username").set(user, "张三1"); userClassByuser.getDeclaredField("password").set(user, "456"); // 反射修改方法 Method setUsername = userClassByuser.getMethod("setUsername", String.class); setUsername.invoke(user, "张三2"); System.out.println(user); } catch (Exception e) { e.printStackTrace(); } } }
打印结果:
User{username='张三', password='123'} User{username='张三2', password='456'}
获取构造器等
获取构造器 | 获取成员变量 | 获取成员方法 | |
---|---|---|---|
非私有 | getConstructor(类型.class) | getMethod(名, 参数.class); | getDeclaredField("id"); |
私有 | getDeclaredConstructor(类型.class)和setAccessible(true) | getDeclaredMethod(名, 参数.class);setAccessible(true) | getDeclaredField("id");setAccessible(true) |
定义测试的User:看到原类的构造器、成员属性、方法,想着怎么使用反射生成
package reflect; public class User { private int id=1; private String name="张三"; private static Date date; public User() { } public User(int id) { this.id = id; } private User(String name) { this.name = name; } public User(int id, String name) { this.id = id; this.name = name; } public void fun1() { System.out.println("无参的fun1被调用"); } public void fun2(int id) { System.out.println("fun2:" + id); } public void fun3(int id, String s) { System.out.println("fun3:" + id + "," + s); } private void fun4(Date date) { System.out.println("fun4:" + date); } public static void fun5() { System.out.println("fun5"); } public static void fun6(String[] args) { System.out.println(args.length); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public static Date getDate() { return date; } public static void setDate(Date date) { User.date = date; } }
public class Demo1 { /** * 获取非私有构造器 */ @Test public void test2() throws Exception { Class<?> userClazz = Class.forName("reflect.User"); Constructor<?> c1 = userClazz.getConstructor(); Constructor<?> c2 = userClazz.getConstructor(int.class); Constructor<?> c3 = userClazz.getConstructor(int.class, String.class); User user = (User) c3.newInstance(1, "A"); System.out.println(user); } /** * 获取私有构造器 */ @Test public void test3() throws Exception { Class<?> userClazz = Class.forName("reflect.User"); // 私有需要declared修饰 Constructor<?> c = userClazz.getDeclaredConstructor(String.class); // setAccessible设置暴露破解 c.setAccessible(true); User user = (User) c.newInstance("A"); System.out.println(user); } /** * 获取所有构造器:私有和非私有 */ @Test public void test4() throws Exception { Class<?> userClazz = Class.forName("reflect.User"); Constructor<?>[] constructors = userClazz.getDeclaredConstructors(); for (Constructor c : constructors) { System.out.println(c); } } }
特殊情况:因为1.4是将字符数组分开作为小个体,String[]
作为方法参数需要(Object)强转/new Object[]{包装}
public class Demo2 { /** * 获取非私有的成员方法 */ @Test public void test1() throws Exception { Class<?> claszz = Class.forName("reflect.User"); User user = (User) claszz.newInstance(); Method fun1 = claszz.getMethod("fun1", null); fun1.invoke(user, null); Method fun2 = claszz.getMethod("fun2", int.class); fun2.invoke(user, 1); Method fun3 = claszz.getMethod("fun3", int.class, String.class); fun3.invoke(user, 1, "A"); } /** * 获得私有方法 */ @Test public void test2() throws Exception { Class<?> claszz = Class.forName("reflect.User"); User user = (User) claszz.newInstance(); // declared修饰private Method fun4 = claszz.getDeclaredMethod("fun4", Date.class); // setAccessible设置暴露破解 fun4.setAccessible(true); fun4.invoke(user, new Date()); } /** * 获得无数组参数的静态方法 */ @Test public void test3() throws Exception { Class<?> claszz = Class.forName("reflect.User"); Method fun5 = claszz.getDeclaredMethod("fun5"); fun5.invoke(null); } /** * 特殊情况:获得String数组参数的静态方法 */ @Test public void test4() throws Exception { Class<?> claszz = Class.forName("reflect.User"); Method fun6 = claszz.getDeclaredMethod("fun6", String[].class); // fun6.invoke(null, new String[]{"1","2"}); 是要报错的,因为JDK4是把字符数组当做一个个对象解析 // 以下两种方式解决: fun6.invoke(null, (Object) new String[]{"1", "2"}); fun6.invoke(null, new Object[]{new String[]{"1", "2"}}); } }
一般来说成员属性都是私有的:getDeclaredField
(setAccessible
)后set
值
public class Demo3 { /** * 获取非静态的私有成员变量 */ @Test public void test1() throws Exception { Class<?> userClass = Class.forName("bean.User"); User user = (User) userClass.newInstance(); Field id = userClass.getDeclaredField("id"); id.setAccessible(true); id.set(user, 2); Field name = userClass.getDeclaredField("name"); name.setAccessible(true); name.set(user, "李四"); System.out.println(user); } /** * 获取静态成员变量 */ @Test public void test2() throws Exception { Class<?> userClass = Class.forName("bean.User"); Field date = userClass.getDeclaredField("date"); date.setAccessible(true); date.set(null, new Date()); System.out.println("User的Date:" + User.getDate()); } }
String.intern问题
public class StringQuestion { /* intern:返回值一个字符串,内容与此字符串相同,但一定取自具有唯一字符串的池 */ public static void main(String[] args) { String str1 = new StringBuilder("58").append("同城").toString(); System.out.println(str1); System.out.println(str1.intern()); System.out.println(str1 == str1.intern()); System.out.println("-----------"); String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str2); System.out.println(str2.intern()); System.out.println(str2 == str2.intern()); } }
58同城 58同城 true ----------- java java false
第一个是true,第二个为什么是false?
因为JDK初始化sun.misc.Version会在常量池自动生成一个“Java”,与剩余生成的”Java“地址肯定不一样。其余字符串都是用户创建才会在常量池生成
public class Version { private static final String launcher_name = "java"; private static final String java_version = "1.8.0_271"; private static final String java_runtime_name = "Java(TM) SE Runtime Environment"; private static final String java_profile_name = ""; ... }
异常分类
异常的概念:异常指在方法不能按照 常方式 ,可以通过抛出异常的方式退出 该方法,在异常中封装了方法执行 程中的错误信息及原因 调用 该异 常后可根据务的情况选择处理该异常或者继续抛出。
异常分类 | 概述 |
---|---|
Error | Java 程序运行错误 ,如果程序在启动时出现 Error 则启 动失败;如果程序在运行过程中出现 Error ,则系统将退出进程 |
Exception | Java 程序运行异常,即运行中的程序发生了人们不期望发生的情况,可以被Java异常机制处理 |
Exception分类 | 解释 | 常见 |
---|---|---|
RuntimeException | Java 虚拟机正常运行期间抛出的异常 | NullPointerException、ClassCastException ArraylndexOutOfBundsException |
CheckedException | 编译阶段 Java 编译器会检查 CheckedException 异常井强调捕获 | IO Exception、SQLExcption、ClassNotFoundException |
捕获异常
public class ThrowException { // 抛出异常的3种方式 // 1.throw:获取方法中的异常,throw 后面的语句块将无法被执行(finally除外) private static void getThrow() { String str = "str"; int index = 10; if (index > str.length()) { throw new StringIndexOutOfBoundsException("index > str.length"); } else { System.out.println(str); } } // 2.throws作用在方法上 private static int getThrows(int a, int b) throws Exception { return a / b; } // 3.tryCatch包裹 private static void getTryCatch() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("最后必须会执行"); } } }
内部类
内部类 | 解释 |
---|---|
静态内部类 | 可以访问外部类的静态变量和方法 |
成员内部类 | 非静态内部类,不能定义静态方法和变量(final除外) |
局部内部类 | 类中方法中定义的一个类 |
匿名内部类 | 继承一个父类或者实现一个接口的方式直接定义并使用的类 |
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(); }
泛型标记
泛型上下限
泛型上下限 | 解释 |
---|---|
<? extends T> | ?是T的子类/T接口的子接口 |
<? super T> | ?是T的父类/T接口的父接口 |
定义泛型
泛型类
泛型类的使用:类名后<?>
public class TDemo<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } public static void main(String[] args) { TDemo<Integer> tDemo1 = new TDemo<>(); TDemo<String> tDemo2 = new TDemo<>(); tDemo1.setValue(1); System.out.println(tDemo1.getValue()); tDemo2.setValue("a"); System.out.println(tDemo2.getValue()); } }
泛型方法
泛型方法使用:在方法返回值前定义泛型<?>,也可以继承一些接口<? extends Comparable<e>></e>
public static <E extends Comparable<E>> void bubbleSort0(E[] arr) { // 只需要n-1层外部循环 for (int i = arr.length - 1; i > 0; i--) { for (int j = 0; j < i && arr[j].compareTo(arr[j + 1]) > 0; j++) { swap(arr, j, j + 1); } } }
泛型接口
接口<?>,其实现类指定类型如implement 接口
public interface TInterfer<T> { public T getId(); }
public class TInterferImpl implements TInterfer<String> { @Override public String getId() { return UUID.randomUUID().toString().substring(0, 3); } }
序列化
名词 | 关键字 | 特性 |
---|---|---|
序列化 | implements Serializable | 底层字节数组,保存对象的状态信息,不能保存静态变量 |
反序列化 | transient | 被该关键字修饰的,不能被反系列化 |