jdk
jdk:是java开发工具包
-
JVM虚拟机:java程序运行的地方
-
核心类库:java已经写好的东西,可以直接使用
-
开发工具:javac java …
JRE:
JRE是java运行环境和javaapi
JVM,核心类库,运行工具
jdk包含jre jre包含jvm
为什么说 Java 语言“编译与解释并存”
编译型语言:
- 编译型语言注重编译,它是指将文件通过编译器一次性编译成特定平台(操作系统)可以运行的机器码。(编译器依赖性较强,可移植性差)
解释型语言:
-
使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行。是代码在执行时才被解释器一行行动态翻译和执行,而不是在执行之前就完成翻译。(编译和解释混合在一起执行.效率较低)
-
java语言为什么两者兼有
- .java经过javac编译器编译之后得到.class文件->这个就是java文件到虚拟机的中间码->.class文件只有被JVM加载->然后通过解释器-逐行解释执行,这种方式的执行速度会相对比较慢。这是属于解释型语言的特点,中间码即.calss文件只是一个中间码,并不算机器码,所以并不满足编译型语言的特点。然而,有些方法和代码块是经常需要被调用的,也就是所谓的热点代码,后面引进了 JIT 编译器,JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。这是属于编译型语言的特点,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
Java注解面试题
Annotation(注解)是Java 1.5引入的一个特性,提供一种对元程序中元素关联信息和元数据的途径和方法。Annotation是一个接口,程序可以通过反射来获取指定程序中元素的Annotation对象,然后通过该Annotation对象来获取注解中的元数据信息。作用是用于取代XML和Properties配置文件。
注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。
java内置了三种标准注解,定义在java.lang中的注解:
1、@Override
表示当前的方法定义将覆盖超类中的方法(重写)。如果你不小心拼写错误,或者方法签名对不上被覆盖的方法,编译器就会发出错误提示。
2、@Deprecated
若某类或某方法加上该注解之后,表示此方法或类不再建议使用,调用时也会出现删除线,但并不代表不能用,只是说,不推荐使用,因为还有更好的方法可以调用
3、@SuppressWarnings
用于抑制编译器产生警告信息。
Java 5.0定义了4个标准的meta-annotation(元注解)类型,元注解的作用是负责注解其他注解或自定义注解的行为和属性。Java中常用的元注解包括:
@Retention:用于指定注解的生命周期,即注解在什么级别可用,一般可选值为RetentionPolicy.SOURCE、CLASS和RUNTIME。
@Target:用于指定注解可以应用于哪些元素上,修饰的是对象的范围,可被用于ElementType.TYPE、METHOD、FIELD、ANNOTATION_TYPE、TYPE_PARAMETER(Java 8)、TYPE_USE(Java 8)、MODULE(Java 9)、RECORD_COMPONENT(Java 16)等。
@Documented:用于指定注解是否应该被包含在JavaDoc中。
@Inherited:是一个标记注解。用于指定注解是否应该被子类继承
ava 的注解也叫作元数据(metadata),是 Java 语言中的一种特殊类型,它可以提供有关程序元素的信息,但不对程序的执行产生任何直接影响。Java 的注解可以应用于类、方法、变量、参数、包等多种程序元素上,其主要使用场景包括以下几种:
\1. 标记注解:用于标记某个类或方法的属性,如:@Deprecated、@Override 等。
\2. 编译时注解:用于在编译时期间处理程序,从而影响生成的字节码文件,如:@SuppressWarnings。
\3. 运行时注解:用于在程序运行时处理程序,从而实现特定的功能,如:@Autowired、@Component 等。
Java 的注解可以通过反射机制获得(实现反射机制的类是 java.lang.Class),这使得程序可以在运行时获取类的注解信息,并且根据注解信息自动执行特定的处理逻辑。在实践中,Java 注解常常被用于框架开发、插件开发、自动化测试、ORM 等场景中,以提高代码的可读性、拓展性和可维护性。
string相关
字符型常量和字符串常量的区别
- 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符
- 含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
- 占内存大小字符常量只占一个字节 字符串常量占若干个字节(至少一个字符结束标志)
什么是字符串常量池?
字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。
String 是最基本的数据类型吗?
不是。Java 中的基本数据类型只有 8 个 :byte、short、int、long、float、 double、char、 boolean.
除了基本类型(primitive type),剩下的都是引用类型(referencetype),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。
这是很基础的东西,但是很多初学者却容易忽视,Java 的 8 种基本数据类型中不包括 String,基本数据 类型中用来描述文本数据的是 char,但是它只能表示单个字符,比如 ‘a’,‘好’ 之类的。
如果要描述一段文本,就需要用多个char 类型的变量,也就是一个 char 类型数组,比如“你好” 就是长度为2的数组 char[] chars = {‘你’,‘好’};但是使用数组过于麻烦,所以就有了 String,String 底层就是一个 char 类型的数组,只是使用的时候开发者不需要直接操作底层数组,用更加简便的方式即可完成对字符串的使用。
String有哪些特性
- 不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。
- 常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。
- final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。
String为什么是不可变的?
简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下:
1 /** The value is used for character storage. */
2 private final char value[];
String真的是不可变的吗?
我觉得如果别人问这个问题的话,回答不可变就可以了。下面只是给大家看两个有代表性的例子:
- String不可变但不代表引用不可以变
1 String str = "Hello";
2 str = str + " World";
3 System.out.println("str=" + str)
结果:
1 str=Hello World
解析:实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。
- 通过反射是可以修改所谓的“不可变”对象
1 // 创建字符串"Hello World", 并赋给引用s
2 String s = "Hello World";
3
4 System.out.println("s = " + s); // Hello World
5
6 // 获取String类中的value字段
7 Field valueFieldOfString = String.class.getDeclaredField("value");
8
9 // 改变value属性的访问权限
10 valueFieldOfString.setAccessible(true);
11
12 // 获取s对象上的value属性的值
13 char[] value = (char[]) valueFieldOfString.get(s);
14
15 // 改变value所引用的数组中的第5个字符
16 value[5] = '_';
17
18 System.out.println("s = " + s); // Hello_World
结果:
1 s = Hello World
2 s = Hello_World
解析:用反射可以访问私有成员, 然后反射出String对象中的value属性,进而改变通过获得的value引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。
是否可以继承 String 类
String 类是 final 类,不可以被继承。
String str="i"与 String str=new String(“i”)一样吗?不一样,因为内存的分配方式不一样。
String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
String s = new String(“xyz”);创建了几个字符串对象两个对象,一个是静态区的"xyz",一个是用 new创建在堆上的对象
1 String str1 = "hello"; //str1指向静态区
2 String str2 = new String("hello"); //str2指向堆上的对象
3 String str3 = "hello";
4 String str4 = new String("hello");
5 System.out.println(str1.equals(str2)); //true
6 System.out.println(str2.equals(str4)); //true
7 System.out.println(str1 == str3); //true
8 System.out.println(str1 == str2); //false
9 System.out.println(str2 == str4); //false
10 System.out.println(str2 == "hello"); //false
11 str2 = str1;
12 System.out.println(str2 == "hello"); //true
如何将字符串反转?使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。示例代码:
1 // StringBuffer reverse
2 StringBuffer stringBuffer = new StringBuffer();
3 stringBuffer. append("abcdefg");
4 System. out. println(stringBuffer. reverse()); // gfedcba
5 // StringBuilder reverse
6 StringBuilder stringBuilder = new StringBuilder();
7 stringBuilder. append("abcdefg");
8 System. out. println(stringBuilder. reverse()); // gfedcba
在使用 HashMap 的时候,用 String 做 key 有什么好处?
HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以 当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。
String和StringBuffer、StringBuilder的区别是什么?String 为什么是不可变的
1.可变性
String类中使用字符数组保存字符串,private final char value[],所以 string对象是不可变的。
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。
2.线程安全性
String中的对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
3.性能
每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。
相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
4.总结
-
如果要操作少量的数据用 = String
-
单线程操作字符串缓冲区下操作大量数据 = StringBuilder
-
多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
什么是泛型
泛型的本质是 参数化类型,也就是说 将所操作的数据类型 指定为一个参数,在不创建新类的情况下,通过参数来指定所要操作的具体类型(类似于方法中的变量参数,此时类型也定义成参数形式),也就是说,在创建对象或者调用方法的时候才明确下具体的类型。可以在类、接口、方法中使用,分别称为泛型类、泛型接口、泛型方法。
泛型的好处
没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。
而引入泛型后,有如下好处:
1、消除显式的强制类型转换,提高代码可读性
泛型中,所有的类型转换都是自动和隐式的,不需要强制类型转换,可以提高代码的重用率,再加上明确的类型信息,代码的可读性也会更好。
2、编译时的类型检查,使程序更加健壮
对于强制类型转换错误的情况,编译期不会提示错误,在运行的时候才出现异常,这是一个安全隐患。泛型的好处是在编译期检查类型安全,并能捕捉类型不匹配的错误,避免运行时抛出类型转化异常ClassCastException,将运行时错误提前到编译时错误,消除安全隐患。
Java类库中的泛型有哪些?泛型的用途?
1、泛型类
最常见的用途就是容器类,通过泛型可以完成对一组类的操作对外开放相同的接口。所有的标准集合接口都是泛型化的—Collection、List、Set 和 Map<K,V>。
2、泛型接口
集合接口的实现都是用相同类型参数泛型化的,所以HashMap<K,V> 实现 Map<K,V> 等都是泛型的,Comparable和Comparator接口也是泛型的。
除了集合类之外,Java 类库中还有几个其他的类也充当值的容器。这些类包括 WeakReference、SoftReference 和 ThreadLocal。
3、泛型方法
要定义泛型方法,只需将泛型参数列表置于返回值之前。
静态方法上的泛型:静态方法无法访问类上定义的泛型。如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
public static<Q> void function(Q t) { System.out.println("function:"+t); }
如果自己要自定义泛型类和泛型方法,请参考:
泛型的上界和下界
<?extends T> 表示类型的上界,参数化类型可能是T 或者是 T的子类;
<? super T> 表示类型的下界,参数化类型是此T类型的超类型,直至object;上界什么时候用:往集合中添加元素时,既可以添加T类型对象,又可以添加T的子类型对象。为什么?因为存的时候,T类型既可以接收T类对象,又可以接收T的子类型对象。
下界什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。
泛型的类型擦除
Java泛型的实现是靠类型擦除技术实现的,类型擦除是在编译期完成的,也就是在编译期,编译器会将泛型的类型参数都擦除成它指定的原始限定类型,如果没有指定的原始限定类型则擦除为Object类型,之后在获取的时候再强制类型转换为对应的类型,因此生成的Java字节码中是不包含泛型中的类型信息的,即运行期间并没有泛型的任何信息。
(1)在使用泛型的时候,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存中只有一个,即还是原来的最基本的类型;泛型只在编译阶段有效,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转化的方法,也就是说,成功编译后的class文件是不包含任何泛型信息的。总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同类型。
(2)泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同的类型。类型参数在运行中并不存在,这意味着他们不会添加任何的时间和空间上的负担;但是,这也意味着不能依靠它们进行类型转换。
类型擦除示例
public class Test4 { public static void main(String[] args) { ArrayList<String> arrayList1=new ArrayList<String>(); arrayList1.add("abc"); ArrayList<Integer> arrayList2=new ArrayList<Integer>(); arrayList2.add(123); System.out.println(arrayList1.getClass()==arrayList2.getClass()); //true } }
在这个例子中,我们定义了两个ArrayList数组,不过一个是ArrayList泛型类型,只能存储字符串。一个是ArrayList泛型类型,只能存储整形。最后,我们通过arrayList1对象和arrayList2对象的getClass方法获取它们的类的信息,最后发现结果为true。说明泛型类型String和Integer都被擦除掉了,只剩下了原始类型。
转型和instanceof
//泛型类被所有实例(instances)共享的另一个暗示是检查一个特定类型的泛型类是没有意义的。 Collection cs = new ArrayList<String>(); if (cs instanceof Collection<String>) { ...} // 非法 类似的,如下的类型转换 Collection<String> cstr = (Collection<String>) cs; 得到一个unchecked warning,因为运行时环境不会为你作这样的检查。
关于泛型的其他一些小细节
1、可以创建泛型数组吗?相应的应用场景怎么处理?
不能创建泛型数组。一般的解决方案是任何想要创建泛型数组的地方都使用ArrayList。
2、可以将基本类型作为泛型参数吗?
泛型的类型参数只能是类类型(包括自定义类),不能是简单类型(基本数据类型)。
3、什么时候用泛型
当接口、类及方法中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。
4、泛型细节
(1)泛型实际代表什么类型,取决于调用者传入的类型,如果没传,默认是Object类型;
(2)使用带泛型的类创建对象时,等式两边指定的泛型类型必须一致。 原因:编译器检查对象调用方法时只看变量,然而程序在运行期间调用方法时就要考虑对象具体类型了。
(3)等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容);
ArrayList<String> al = new ArrayList<Object>(); //错 //要保证左右两边的泛型具体类型一致就可以了,这样不容易出错。 ArrayList<?extends Object> al = new ArrayList<String>(); al.add("aa"); //错 //因为集合具体对象中既可存储String,也可以存储Object的其他子类,所以添加具体的类型对象不合适,类型检查会出现安全问题
对于==而言
==对基本数据类型比较的是值
==对引用数据类型比较的是地址
对于equals方法
Object没有重写的equals方法比较的是地址
String重写的equals方法比较的是字符串是否相同
Integer重写了equals方法,比较的是int值是否相同
延申:对于基本数据类型和包装类的==比较(7种)
如果int和Interger进行比较
无论Interger是new还是直接赋一个int值
integer都会自动拆箱
比较的是两者的int值是否相等
Integer i1=128,int i2=128
i1==i2为true,
Integer i1=new Integer(128),int i2=128
i1==i2为true,
如果是使用关键字new
Integer i1=new Integer(2)
Integer i2=new Integer(2)
那么,地址一定是新开辟的
i1==i2比较为false
Integer i1=127,i2=127
i1==i2为true,
因为-128-127是byte的取值范围,在此范围内Integer会直接拆箱,从常量池里面直接获取,在内存里用原生的int基本数据类型,进行值的比较,
如果Integer i1=128,i2=128
i1==i2为false,
但是128不在常量池指定的byte范围,会新建一个对象
如果Integer i1=new Integer(2),i2=2
i1==i2为false,
任何new操作都会创建新的地址
==比较必然为false,但是equals要看方法是否被该包装类重写
如果Integer i1=128,i2=128
i1.equals(i2)为true,
Integer的equals方法进行了重写,比较的是int值
一、Java基本数据类型
基本数据类型有8种:byte、short、int、long、float、double、boolean、char
分为4类:整数型、浮点型、布尔型、字符型。
整数型:byte、short、int、long
浮点型:float、double
布尔型:boolean
字符型:char
二、各数据类型所占字节大小
2.1Java中的字节
1个字节8位
计算机的基本单位:bit (位). 一个bit代表一个0或1
byte:1byte(字节) = 8bit(位) 1个字节是8个bit
2.2不同数据类型的字节大小
byte(8位 1字节)
short(16位 2字节)
int(32位 4字节)
long(64位 8字节)
double(64位 8字节)
float(32位 4字节)
char(16位 2字节)
boolean(8位 1字节)
2.3在Java中,变量有两种类型,一种是原始(基本)类型,一种是引用类型。
原始类型一共有8种,它们分别是char,boolean,byte,short,int,long,float,double。在Java API中,有它们对应的包装类:
分别是(首字母大写)Character,Boolean,Byte,Short,Integer,Long,Float,Double(char,int的变化稍微大点)。
JAVA JVM对于不同的原始类型会分配不同的存储空间,具体分配如下:
byte : 1个字节 8位
最大值: 127 (有符号)
short : 2个字节 16位 32767
int : 4个字节 32位 2147483647
long: 8个字节 64位 9223372036854775807
float: 4个字节 32位 3.4028235E38
double:8个字节 64位 1.7976931348623157E308
枚举(enum)类型是Java 5新增的特性,它是一种新的类型,允许用常量来表示特定的数据片断,而且全部都以类型安全的形式来表示,是特殊的类,可以拥有成员变量和方法
equals与==的区别
对于==而言
==对基本数据类型比较的是值
==对引用数据类型比较的是地址
对于equals方法
Object没有重写的equals方法比较的是地址
String重写的equals方法比较的是字符串是否相同
Integer重写了equals方法,比较的是int值是否相同
延申:对于基本数据类型和包装类的==比较(7种)
如果int和Interger进行比较
无论Interger是new还是直接赋一个int值
integer都会自动拆箱
比较的是两者的int值是否相等
Integer i1=128,int i2=128
i1==i2为true,
Integer i1=new Integer(128),int i2=128
i1==i2为true,
如果是使用关键字new
Integer i1=new Integer(2)
Integer i2=new Integer(2)
那么,地址一定是新开辟的
i1==i2比较为false
Integer i1=127,i2=127
i1==i2为true,
因为-128-127是byte的取值范围,在此范围内Integer会直接拆箱,从常量池里面直接获取,在内存里用原生的int基本数据类型,进行值的比较,
如果Integer i1=128,i2=128
i1==i2为false,
但是128不在常量池指定的byte范围,会新建一个对象
如果Integer i1=new Integer(2),i2=2
i1==i2为false,
任何new操作都会创建新的地址
==比较必然为false,但是equals要看方法是否被该包装类重写
如果Integer i1=128,i2=128
i1.equals(i2)为true,
Integer的equals方法进行了重写,比较的是int值
方法的分类
一,一般方法
二,构造方法
三,无参无返回值的方法。
四,无参有返回值的方法。
五,有参无返回值的方法。
六,有参有返回值的方法。
七,静态方法。
八,非静态方法。
九,抽象方法。
关键字
什么事关键字?
被java赋予特定含义的英文字母
关键字的特点:
关键字的字母全部小写
常见的代码编辑器对关键字有特殊颜色标记
class关键字是什么意思?
表明定义一个类后面跟随类名
static
静态成员有static修饰,属于类本身,与类加载一次,因为只有一份可以用类和对象同时访问,不推荐用对象访问
a.实例方法是否可以直接访问实例成员变量
true
b.实例访问是否可以直接访问静态成员方法
true
c.实例方法是否能访问实例方法
true
d实例方法是否可以直接访问静态方法
true
a静态方法能否访问实例对象
false 只能通过实例对象访问
b.静态方法是都可以直接访问静态变量
true
c.静态方法是都可以直接访问实例化方法
不行
d静态方法是否可以直接访问静态方法
能
特殊符号
/t 制表符,把前面字符串的长度补齐到8,或者8的整数倍.最少补一个空格,最多补8个
进制
二进制:代码中以0b开头
十进制:0-9不加前缀
八进制:0-7代码中以0开头
十六进制:由0-9和a-f组成以0x开头
java面向对象的特征有哪些?
面向对象是利用类和对象编程的一种思想,万物可归类,类是对世界事务的高度抽象,不同的事物之间有不同的关系,
一个类自身与外界的封装关系,子类和父类的集成关系,一个类和多个类的多态关系.万物皆对象,对象是具体的世界事务
面向对象的三大特征:封装,继承,多态.封装说明一个类行为和属性与其他类的关系,低耦合,高内聚;继承是子类和父类的关系
多态说明的是类与类之间的关系
1.继承
是一种特殊关系,是一种子类到父类的关系
被继承的类成为:父类/超类
继承父类的成为:子类
子类不能继承父类的构造器
有争议子类是否可以继承父类的私有成员(私有成员方法,私有成员方法)
可以继承,不能直接访问
子类是否可以继承父类的静态成员
子类不能继承父类的静态成员,可以被子类共享访问,共享非继承
2.多态
3.封装
-
可以提高安全性
-
可以实现代码的组件化
封装的规范:
建议成员变量都私有话
提供成套的getter和setter方法
this关键字
作用:
this代表的了当前对象的引用
this关键字可以用在构造器和实例化方法中
this用在方法中谁调用这个方法this就代表谁
this用在构造器代表了构造器正在初始化的那个对象的引用
super关键字
表示父类的对象的引用
1.重写(Override):
从字面上看,重写就是 重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。
2.重载(Overload):
在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
重载与重写的区别:
1、重载发生在本类,重写发生在父类与子类之间;
2、重载的方法名必须相同,重写的方法名相同且返回值类型必须相同;
3、重载的参数列表不同,重写的参数列表必须相同。
深拷贝和浅拷贝
一、深拷贝与浅拷贝
深浅拷贝针对的都是引用类型
1.浅拷贝:拷贝基本数据类型时不会受到影响
拷贝引用对象时,原对象也会被修改
仅拷贝对象地址
白话文:假如b赋值a,当修改a时,b也会跟着变化
a1=[1,2,3]
a2=a1
console.log(a1,a2);
a1.push(4)
console.log(a1,a2);//a1=[1,2,3,4] a2=[1,2,3,4]
2.深拷贝:深拷贝是指拷贝一个对象的数据之前,先给拷贝的对象创建一个堆地址,这样当拷贝对象指向的堆中的数据改变时,
被拷贝的对象,堆中的数据不会改变
白话文:b赋值a,当修改a时b没变
成员变量和局部变量
成员变量是在类内部定义的变量,在类的任何方法中都可以直接使用,其作用域为整个类。成员变量有默认值,如果没有给定初始值,数值类型默认为0,布尔类型默认为false,对象类型默认为null。
局部变量是在方法、代码块、循环等内部定义的变量,其作用域只限于当前代码块内。局部变量必须显式地定义类型并且必须初始化才能使用。
成员变量和局部变量的主要区别如下:
1、作用范围不同:成员变量作用于整个类,而局部变量作用于当前代码块。
2、生命周期不同:成员变量的生命周期与对象相同,当创建对象时会被初始化,直到对象被销毁时才会被销毁;而局部变量的生命周期在定义时开始,在代码块结束时结束。
3、初始值不同:成员变量有默认值,而局部变量必须显式地定义类型并且必须初始化才能使用。
4、权限不同:成员变量可以设定不同的权限修饰符,可以被其他对象的方法和代码块访问;而局部变量的访问权限只限于定义它的代码块内。
Java–对象实体与对象引用有何不同
通过new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。
对象的相等与指向他们的引用相等,两者有什么不同?
对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。]
构造方法的特点和作用
(1)构造方法名一定与类同名。
(2)构造方法无返回值类型(void也不行)
(3)构造方法可以没有(默认一个无参构造方法),也可以有多个构造方法。他们之间构成重载关系。
(4)如果定义有参构造函数,则无参构造函数将被自动屏蔽。
(5)构造方法不能被继承。
(6)构造方法不能手动调用,在创建类实例的时候自动调用构造方法。
作用:
(1)初始化对象,为对象赋初值。
(2)简化我们为类字段赋值的代码。
7.简述构造方法和普通方法的区别:
(1)构造方法一定与类同名,普通方法就可以不用。
(2)构造方法无返回值类型(void也不行),普通方法可以返回
导言
Java反射是指在运行时动态地获取类的信息,并可以通过该信息来操作类或对象。通过反射,我们可以在运行时获取类的字段、方法、构造函数等信息,并能够动态地创建对象、调用方法、访问和修改字段的值。本文将详细介绍Java反射的概念、原理和使用方法,并提供一些示例代码。
一、反射的概念
反射是Java语言的一种机制,它允许程序在运行时检查和操作类、方法、字段等信息,而不需要提前知道它们的具体定义。通过反射,我们可以在运行时动态地加载类、创建对象、调用方法以及访问和修改字段。
Java反射提供了以下核心类:
- Class类:代表Java中的类或接口。通过Class类,我们可以获取类的构造函数、方法、字段等信息。
- Constructor类:代表类的构造函数。通过Constructor类,我们可以创建对象。
- Method类:代表类的方法。通过Method类,我们可以调用方法。
- Field类:代表类的字段。通过Field类,我们可以访问和修改字段的值。
反射是Java强大的特性之一,它在很多框架和工具中被广泛应用,如Spring框架、JUnit测试框架等。
二、反射的原理
Java反射的原理基于Java的运行时数据区域(Runtime Data Area)和类加载机制。当Java虚拟机加载一个类时,它将类的字节码文件加载到内存中,并在方法区创建一个Class对象来表示该类。Class对象包含了类的完整信息,包括类的构造函数、方法、字段等。
通过反射,我们可以通过Class对象来获取类的信息,并在运行时进行操作。反射提供了一系列的方法来获取Class对象、获取构造函数、获取方法、获取字段等。
三、反射的使用示例
下面是一个简单的示例代码,演示了如何使用Java反射来创建对象、调用方法和访问字段:
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 获取Class对象
Class<?> clazz = Class.forName("com.example.MyClass");
// 创建对象
Object object = clazz.newInstance();
// 调用方法
Method method = clazz.getDeclaredMethod("sayHello");
method.invoke(object);
// 访问字段
Field field = clazz.getDeclaredField("message");
field.setAccessible(true);
field.set(object, "Hello, Reflection!");
// 再次调用方法,输出修改后的字段值
method.invoke(object);
}
}
class MyClass {
private String message = "Hello, World!";
public void sayHello() {
System.out.println(message);
}
}
在上述示例中,我们使用反射的方式创建了一个名为com.example.MyClass
的类的对象,并调用了其中的sayHello
方法。然后,我们通过反射访问了该类的私有字段message
并修改了其值。最后,再次调用sayHello
方法,输出修改后的字段值。
通过这个简单的示例,我们可以看到反射的强大之处。它允许我们在运行时动态地创建对象、调用方法以及访问和修改字段,而不需要提前知道类的具体定义。
四、反射的应用场景
反射在Java中有许多应用场景,以下是一些常见的使用情况:
- 框架和库:许多Java框架和库使用反射来实现动态加载和配置。例如,Spring框架使用反射来实现依赖注入和AOP编程。
- 序列化和反序列化:Java的序列化和反序列化机制使用了反射。通过反射,可以在运行时动态地读取和写入对象的字段。
- 单元测试:JUnit等单元测试框架使用反射来自动化执行测试用例。通过反射,测试框架可以自动发现和执行类中的测试方法。
- 动态代理:Java动态代理机制利用了反射来实现代理对象的动态创建和方法调用的拦截。
这些只是反射的一些应用场景,实际上,反射在Java的开发中具有广泛的应用。
五、反射的注意事项
在使用反射时,我们需要注意以下几点:
- 性能开销:反射的操作相比普通的Java代码会有一定的性能开销。因此,在性能要求较高的场景下,应尽量避免过度使用反射。
- 访问权限:通过反射可以访问和修改类的私有成员,但这可能违反了类的封装性。在使用反射时,应注意尊重类的访问权限。
- 异常处理:使用反射时,可能会抛出ClassNotFoundException、NoSuchMethodException等异常。在使用反射的代码中,要适当地处理这些异常。
总结
Java反射是一种强大的特性,它允许程序在运行时动态地获取和操作类的信息。通过反射,我们可以创建对象、调用方法和访问字段,而不需要提前知道类的具体定义。反射在许多框架和工具中被广泛应用,具有重要的作用。
希望本文对你理解和使用Java反射有所帮助!通过灵活应用反射机制,你可以在Java开发中更加灵活和高效地实现各种功能。
io
反序列化
Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。
序列化和反序列化的作用
序列化:就是将数据结构或者对象转化为字节流
使用场景:
-
序列化可以保存内存中各种对象的状态,并且可以把对象的状态再读出来
-
可以实现持久化存储,在mvc模式中很有作用
-
对象的远程同行用的是序列化和反序列化
序列化是一种将对象转换为字节流以便于存储、传输或持久化的机制,但它也有一些缺点和局限性,包括以下几个方面:- 版本兼容性问题:当序列化的对象的类发生变化(例如添加、删除或修改字段或方法)时,之前序列化的对象可能无法正确反序列化,导致版本兼容性问题。要解决这个问题,需要使用序列化版本号、自定义序列化方式或者考虑其他序列化机制。
- 安全性问题:默认的Java序列化机制不提供足够的安全性,因为它允许序列化的对象中包含敏感数据。攻击者可以通过反序列化恶意数据来执行代码注入攻击,因此需要特别小心处理来自不受信任源的序列化数据。
- 性能开销:序列化和反序列化过程通常会引入性能开销,特别是对于大型对象或频繁进行序列化/反序列化操作的场景。序列化需要将对象转换为字节流,而反序列化需要将字节流转换回对象,这些操作可能涉及复杂的数据结构和类型检查。
- 可读性差:序列化后的数据通常是二进制的,不可读,不易于调试和查看。这使得调试和维护复杂的序列化数据更加困难。
- 平台依赖性:序列化通常是平台相关的,这意味着在不同的Java虚拟机版本之间,甚至在不同的平台之间,序列化的数据可能不兼容。
- 大小问题:序列化后的数据通常比原始对象更大,这可能导致存储和网络传输的额外开销。如果需要优化存储或传输效率,可能需要考虑其他序列化格式,如JSON或Protocol Buffers。
实现键盘输入的几种方式
1.使用Scanner类
2.使用BufferedReader类:
3.使用Console类(仅适用于命令行应用程序):
4,使用system.in.read流来读入
内部类
定义在一个类中的内部,把这个类叫做内部类
1.成员内部类
成员内部类定义在另一个类的内部
class Outer{
class Inner{}
}
特点:
- inner在编译之后会以独立的class文件存在,class文件的命名方式为Outer&Inner.class
- inner的实例对象不能单独存在,必须依附于outer的实例对象\
- inner可以访问outer中的属性和方法,包括私有属性和私有方法
- inner类具有默认的包的访问权限,只有所在报的类可以访问
- inner类中不能存在static修饰属性和方法(静态常量除外)
2.静态内部类
静态内部类定义在一个类的内部,并用static关键字修饰
特点:
StaticInner在编译之后以独立的class文件存在,class文件的命名方式为:Outer$StaticInner.class
。
2) StaticInner的实例对象可以独立存在,不再依附于Outer的实例对象。
3) StaticInner只能访问Outer中static修饰属性和方法,包括私有属性和私有方法。
4) StaticInner类具有默认的包访问权限,只有所在包的类可以访问。
5) StaticInner类中可以存在static修饰属性和方法。
3.局部内部类
局部内部类与成员内部类类似,但其定义在方法之中,作用范围仅限于该方法中。
局部内部类的特点:
1) LocalInner在编译之后以独立的class文件存在,class文件的命名方式为:Outer$1LocalInner.class
。
2) LocalInner实例对象存在一个隐式引用,指向创建它的Outer类的实例对象。
3) LocalInner除了可以访问Outer中属性和方法,包括私有属性和私有方法,还有访问所处方法中局部变量,但这些变量实际上是final
修饰,不可再改变。
4) LocalInner只能在定义它的方法中访问
5) LocalInner类中不可以存在static修饰属性和方法,静态常量除外。
4.匿名内部类
为什么叫匿名内部类呢?因为匿名内部类不存在类名。之所以我们要定义匿名内部类,是因为我们在使用的时候通常不关系类名,可以省略掉定义类的过程。
匿名内部类的特点:
1) 匿名内部类在编译之后以独立的class文件存在,class文件的命名方式为:外部类名称$数字编号.class
,编号从1开始,文中生成的class文件名为Outer$1.class
。
2) 匿名内部类实例对象存在一个隐式引用,指向创建它的Outer类的实例对象。
3) 匿名内部类可以访问Outer中属性和方法,包括私有属性和私有方法。
4) 匿名内部类类具有默认的包访问权限,只有所在包的类可以访问。
5) 匿名内部类类中不能存在static修饰属性和方法(静态常量除外),是Java语法的一种约束。
存在static修饰属性和方法(静态常量除外)
2.静态内部类
静态内部类定义在一个类的内部,并用static关键字修饰
特点:
StaticInner在编译之后以独立的class文件存在,class文件的命名方式为:Outer$StaticInner.class
。
2) StaticInner的实例对象可以独立存在,不再依附于Outer的实例对象。
3) StaticInner只能访问Outer中static修饰属性和方法,包括私有属性和私有方法。
4) StaticInner类具有默认的包访问权限,只有所在包的类可以访问。
5) StaticInner类中可以存在static修饰属性和方法。
3.局部内部类
局部内部类与成员内部类类似,但其定义在方法之中,作用范围仅限于该方法中。
局部内部类的特点:
1) LocalInner在编译之后以独立的class文件存在,class文件的命名方式为:Outer$1LocalInner.class
。
2) LocalInner实例对象存在一个隐式引用,指向创建它的Outer类的实例对象。
3) LocalInner除了可以访问Outer中属性和方法,包括私有属性和私有方法,还有访问所处方法中局部变量,但这些变量实际上是final
修饰,不可再改变。
4) LocalInner只能在定义它的方法中访问
5) LocalInner类中不可以存在static修饰属性和方法,静态常量除外。
4.匿名内部类
为什么叫匿名内部类呢?因为匿名内部类不存在类名。之所以我们要定义匿名内部类,是因为我们在使用的时候通常不关系类名,可以省略掉定义类的过程。
匿名内部类的特点:
1) 匿名内部类在编译之后以独立的class文件存在,class文件的命名方式为:外部类名称$数字编号.class
,编号从1开始,文中生成的class文件名为Outer$1.class
。
2) 匿名内部类实例对象存在一个隐式引用,指向创建它的Outer类的实例对象。
3) 匿名内部类可以访问Outer中属性和方法,包括私有属性和私有方法。
4) 匿名内部类类具有默认的包访问权限,只有所在包的类可以访问。
5) 匿名内部类类中不能存在static修饰属性和方法(静态常量除外),是Java语法的一种约束。