###Java数组初始化默认值
数据类型 | 明细 | 默认值 |
---|---|---|
基本类型 | byte、short、char、int、long | 0 |
float、double | 0.0 | |
boolean | false | |
引用类型 | 类、接口、数组、string | null |
数组的初始化:
- 静态初始化:开始就存入元素值,适合一开始就能确定元素值的业务场景。
例如: int[] arr = {12, 24, 36}; - 动态初始化:只指定数组长度,后期赋值,适合开始知道数据的数量,但是不确定具体元素值的业务场景。
例如:int[] arr = new int[3] - 两种格式是独立的不能混用:例如:int[] arrs = new int[3]{30,40,50}; 不能这样写
数组中存储的元素并不是对象本身,而是对象的地址
堆内存,栈内存
栈内存 | 堆内存 |
---|---|
方法的运行在栈中 | new出来的存在堆中 |
变量存储在栈中 | 对象存储在堆内存中 |
局部变量存储在栈内存中 | 对象中的成员变量存储在堆内存中 |
注意:当堆内存中的对象,没有被任何变量引用(指向)时,就会被判定为内存中的“垃圾”。就会被垃圾回收器定期回收 | |
以“”方式给出的字符串对象,在字符串常量池中存储。字符串常量池是在堆中开辟的一块地方 |
成员变量和局部变量
区别 | 成员变量 | 局部变量 |
---|---|---|
类中的位置不同 | 类中,方法外 | 常见于方法中 |
初始化值不同 | 有默认值,无需初始化 | 没有默认值,使用之前需要完成赋值 |
内存位置不同 | 堆内存 | 栈内存 |
生命周期不同 | 随着对象的创建而存在,随着对象的消失而消失 | 随着方法的调用而存在,随着方法的运行结束而消失 |
作用域 | 整个类中 | 在所归属的大括号中 |
构造器:
- 一旦定义了有参数构造器,无参数构造器就没有了,此时就需要自己写无参数构造器了。
子类继承父类后构造器的特点:
- 子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己。
- 子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。
- 子类构造器的第一行语句默认都是:super(),不写也存在。
方法重写:
在继承体系中,子类出现了和父类一模一样的方法声明(方法名,参数列表,返回值类型)
方法重载:
- 同一个类中,方法名称相同、形参列表不同,那么他们就是重载的方法,其他都不管!(如:修饰符,返回值类型都无所谓)
- 形参列表不同指的是:形参的个数、类型、顺序不同,不关心形参的名称。
方法重载的作用:
- 可读性好,方法名称相同提示是同一类型的功能,通过形参不同实现功能差异化的选择,这是一种专业的代码设计。
权限修饰符:
修饰符 | 同一类中 | 同一包中的子类,无关类 | 不同包的子类 | 不同包的无关类 |
---|---|---|---|---|
private | ✅ | |||
默认 | ✅ | ✅ | ||
protected | ✅ | ✅ | ✅ | |
public | ✅ | ✅ | ✅ | ✅ |
标准JavaBean须满足如下书写要求:
- 成员变量使用 private 修饰。
- 提供成员变量对应的 setXxx() / getXxx()方法。
- 必须提供一个无参构造器;有参数构造器是可写可不写的。
String类的特点详解
- String其实常被称为不可变字符串类型,它的对象在创建后不能被更改。
- String变量每次的修改其实都是产生并指向了新的字符串对象。
- 原来的字符串对象都是没有改变的,所以称不可变字符串。
字符串类型的比较:使用String提供的equals方法而不是== ,基本数据类型比较时使用==。
创建字符串对象的两种方式(面试题)
- 方式一:直接使用“”定义 例如:String name = “aa教育”;
特点:以“”方式给出的字符串对象,在字符串常量池中存储,而且相同内容只会在其中存储一份。 - 方式二:通过String类的构造器创建对象。
特点:通过构造器new对象,每new一次都会产生一个新对象,放在堆内存中。
ArrayList
- ArrayList代表的是集合类,集合是一种容器,与数组类似,不同的是集合的大小是不固定的。
- 通过创建ArrayList的对象表示得到一个集合容器,同时ArrayList提供了比数组更好用,更丰富的API (功能)给程序员使用。
数组和集合的区别
数组 | 集合 |
---|---|
数组定义后类型确定,长度固定 | 集合类型可以不固定,大小是可变的 |
数组适合做数据个数和类型确定的场景 | 集合适合做数据个数不确定,且要做增删元素的场景 |
泛型:
ArrayList:其实就是一个泛型类,可以在编译阶段约束集合对象只能操作某种数据类型。
ArrayList :此集合只能操作字符串类型的元素。
ArrayList:此集合只能操作整数类型的元素。
注意:泛型只能支持引用数据类型,不支持基本数据类型。
string和stringbuilder的区别:
string内容是不可变的,stringbuilder的内容是可变的
StringBuilder是一个可变的字符串的操作类,我们可以把它看成是一个对象容器。
使用StringBuilder的核心作用:操作字符串的性能比String要更高(如拼接、修改等)。
结论:当需要进行字符串操作的时候如:拼接、修改等操作字符串时应该选择StringBuilder来完成,性能更好。只是定义字符串时使用String
单例设计模式
1、饿汉单例设计模式:在用类获取对象的时候,对象已经提前为你创建好了。
设计步骤:
- 定义一个类,并把构造器私有。
- 定义一个静态变量存储私有的这个私有的构造器对象。
例如:
/** a、定义一个单例类 */
public class SingleInstance {
/** b.单例必须私有构造器*/
private SingleInstance (){
System.out.println("创建了一个对象");
}
/** c.定义一个静态变量存储一个对象即可 :属于类,与类一起加载一次 */
public static SingleInstance instance = new SingleInstance ();
}
2、懒汉单例设计模式:在真正需要该对象的时候,才去创建一个对象(延迟加载对象)。
设计步骤:
- 定义一个类,把构造器私有。
- 定义一个静态变量用于存储私有的构造器对象。
- 提供一个返回单例对象的方法
例如:
/** 定义一个单例类 */
class SingleInstance{
/** 定义一个静态变量存储一个对象即可 :属于类,与类一起加载一次 */
public static SingleInstance instance ; // null
/** 单例必须私有构造器*/
private SingleInstance(){}
/** 必须提供一个方法返回一个单例对象 */
public static SingleInstance getInstance(){
...
return ...;
}
}
继承的特点
- 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器。
- Java是单继承模式:一个类只能继承一个直接父类。
- Java不支持多继承、但是支持多层继承。
- Java中所有的类都是Object类的子类。
- 子类可以继承父类的私有成员但是不能直接访问
枚举
方法重写注意事项和要求
- 重写方法的名称、形参列表必须与被重写方法的名称和参数列表一致。
- 私有方法不能被重写。
- 子类重写父类方法时,访问权限必须大于或者等于父类 (缺省 < protected < public)
- 子类不能重写父类的静态方法,如果重写会报错的。
final修饰的特点:
- 修饰方法:表明该方法是最终方法,不能被重写
- 修饰变量:final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的。
- 修饰类:表明该类是最终类,不能被继承
static修饰的特点:
- 被类的所有对象共享(也即唯一)–这也是我们判断是否使用静态这个关键字的条件
- 可以通过类名调用,当然也可以通过对象名调用
- 被静态修饰的方法,只能访问静态成员变量和静态成员方法,即只能访问静态成员
多态:不同的子类对象调用相同的父类方法而产生不同的结果
注意:
1、如果是父类对象调用父类中的成员变量,即使是在多态这种情况下结果依然是输出父类的成员变量中的值,而不是子类成员变量中的值,因为子类并没有重写父类中的成员变量。
2、多态的情况下父类依然是不能调用子类中独有的成员方法和成员变量。
多态的好处:提高了程序的扩展性
具体体现是:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作。
多态的弊端:不能使用子类的特有功能
多态转型:
向上转型:父类引用指向子类对象
Animal a = new Cat()
不能调用子类方法和子类属性
向下转型:父类引用转为子类对象
Animal a = new Cat()
Cat c = (Cat) a
可以调用子类方法和子类属性
抽象类:
public abstract class Animal{ // 抽象类
public abstract void run(); // 抽象方法
}
在Java中,一个没有方法体的方法应该被定义为抽象方法,而类中如果有抽象方法,则该类必须被定义为抽象类,抽象类中可以拥有非抽象方法。
抽象类不能被直接实例化,可以参照多态的方式,通过子类对象实例化,这叫抽象类多态
抽象类的子类:要么重写抽象类中的所有抽象方法,要么是抽象类。
final和abstract的关系
- 互斥关系
- abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承。
- 抽象方法定义通用功能让子类重写,final定义的方法子类不能重写。
接口:
接口用关键字interface来定义
public interface 接口名 {
// 常量
// 抽象方法
}
2、类实现接口用implements:
public class 类名 implements 接口名 {}
3、接口不能实例化 即不能new接口
接口如何实例化?参照多态方式,通过实现类对象实例化,这叫接口多态。
多态的形式:具体类多态,抽象类多态,接口多态。
多态的前提:有继承或者实现关系;有方法重写;有父(类/接口)引用指向(子/实现)类对象。
接口的实现类:要么重写接口中的所有抽象方法,要么是抽象类
4、接口中的成员都是public修饰的,写不写都是,因为规范的目的是为了公开化。
接口的成员特点:
1、成员变量只能是常量即以public static final开头修饰的,如果没写则默认以public static final开头
2、构造方法:接口没有构造方法,因为接口主要是对行为进行抽象的,是没有具体存在的
3、成员方法:只能是抽象方法即默认以public abstract开头即使不写也是以public abstract开头
抽象类和接口的区别:
1、成员区别:
- 抽象类:有变量,常量;有构造方法,有抽象方法,也可以有非抽象方法;
- 接口:只有常量,抽象方法;
2、关系区别:
- 类与类:之间只能是单继承;
- 类与接口:可以单继承单实现,可以但单继承多实现;
- 接口与接口:可以单继承,也可以多继承
3、设计理念区别:
- 抽象类:对类抽象包括行为和属性;
- 接口:对行为抽象,主要是行为;
内部类的访问特点:
1、内部类可以直接访问外部类的成员,包括私有成员;
2、外部类想要访问内部类的成员必须创建对象;
创建内部类对象格式:
外部类.内部类 对象名 = new 外部类名().new 内部类名();
静态内部类:
外部类.内部类 对象名 = new 外部类名.内部类名();
匿名内部类:
Employee a = new Employee() {
public void work() {"干自己的事情"}
};
a. work();
-----------------------------------------------------------
package com.scanner;
public class Demo {
public static void main(String[] args) {
new Test(){
@Override
public void test() {
System.out.println("测试");
}
}.test();
}
}
interface Test {
void test();
}
匿名内部类特点:
- 匿名内部类是一个没有名字的内部类,同时也代表一个对象。
- 匿名内部类产生的对象类型,相当于是当前new的那个的类型的子类类型。
- 匿名内部类可以作为一个对象,直接传输给方法。
lambda:
package com.scanner;
public class Demo {
public static void main(String[] args) {
test_anonymous_class(()->{
System.out.println("测试lambda和匿名内部类1");
});
// 光标放到Test上按下option+回车会提示用lambda替换掉匿名内部类
test_anonymous_class(new Test() {
@Override
public void test() {
System.out.println("测试lambda和匿名内部类2");
}
});
}
public static void test_anonymous_class(Test test){
test.test();
}
}
interface Test {
void test();
}
lambda的使用前提:
- 有个接口
- 接口中有且仅有一个抽象方法
lambda 和匿名内部类的区别:
1、所需类型不同:
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- lambda表达式:只能是接口
2、使用限制不同:
- 如果接口中有且仅有一个抽象方法,可以使用lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用lambda表达式
3、实现原理不同:
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- lambda表达式:编译之后,没有一个单独的.class字节码文件,对应的字节码会在运行的时候动态生成
Lambda表达式的省略写法(进一步在Lambda表达式的基础上继续简化)
- 参数类型可以省略不写。
- 如果只有一个参数,参数类型可以省略,同时()也可以省略。
- 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!
- 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写
基本类型和包装类(引用数据类型)
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
为什么提供包装类?
- Java为了实现一切皆对象,为8种基本类型提供了对应的引用类型。
- 后面的集合和泛型其实也只能支持包装类型,不支持基本数据类型。
自动装箱:把一个基本数据类型变成对应的包装类
例如: Integer i = 10;
自动拆箱:把一个包装类型变成对应的基本数据类型
例如: int i2 = i;
编译时异常因为在编译时就会检查,所以必须要写在方法后面进行显示声明,运行时异常因为在运行时才会发生,所以在方法后面可以不写
集合类的特点:提供一种存储空间可变的存储模式,存储的数据的容量可随时发生改变
集合和数组的对比:
- 数组的长度是不可变的,集合的长度是可变的
- 数组可以存储基本数据类型和引用数据类型,集合只能存储引用数据类型,如果要存储基本数据类型,需要存对应的包装类。
三种循环遍历的使用场景:
- 如果需要操作索引,使用普通for循环即可
形如:
String[] arr = { "a", "b", "c", "d" };
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
- 如果在遍历过程中需要删除元素,请使用迭代器方式
List<String> list = Arrays.asList(arr);
for (Iterator<String> iterator = list.iterator();iterator.hasNext();) {
System.out.println(iterator.next());
}
- 如果仅仅想遍历,请使用增强for循环
for(int ele : list) {
System.out.println(ele);
}
那些遍历删除元素存在问题?
- 迭代器遍历集合且直接用集合删除元素的时候可能出现。
- 增强for循环遍历集合且直接用集合删除元素的时候可能出现。
哪种遍历且删除元素不出问题
- 迭代器遍历集合但是用迭代器自己的删除方法操作可以解决。
- 使用for循环遍历并删除元素不会存在这个问题。
collection | |||
---|---|---|---|
List (添加的元素是有序、可重复、有索引) | Set (添加的元素是无序、不重复、无索引) | ||
ArrayList (查询多用它) | LinkList (增删多用它) | HashSet (无序、不重复、无索引) | TreeSet (按照大小默认升序排序、不重复、无索引) |
LinkHashSet (有序、不重复、无索引) |
ArrayList底层是基于数组实现的,根据查询元素快,增删相对慢。
LinkedList底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的。
LinkedHashSet:这里的有序指的是保证存储和取出的元素顺序一致
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
TreeSet自定义排序规则的两种实现方式:
- 方式一
让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。 - 方式二
TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。
注意:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序
list 集合特点:
- 有序:存储和取出元素的顺序一致
- 有索引:可以通过索引操作元素
- 可重复:存储的元素可以重复
数组是一种查询快,增删慢的模型,链表是一种增删快的模型
ArrayList:底层数据结构是数组,查询快,增删慢
LinkList: 底层数据结构是链表,查询慢,增删快
泛型的好处:
- 把运行时期的问题提前到了编译时期
- 避免了强制类型转换
- 统一数据类型。
注意:集合和泛型不支持基本类型,只支持引用数据类型。
泛型中的通配符:?
- ? 可以在“使用泛型”的时候代表一切类型。
- E T K V 是在定义泛型的时候使用的。
- ? extends Car表示?必须是Car或者其子类 这叫泛型上限
- ? super Car表示 ?必须是Car或者其父类 这叫泛型下限
可变参数:
- 可变参数的格式:数据类型…参数名称
- 一个形参列表中可变参数只能有一个
- 可变参数必须放在形参列表的最后面
- 可变参数在方法内部本质上就是一个数组。
- 接收参数非常灵活,方便。可以不接收参数,可以接收1个或者多个参数,也可以接收一个数组
Set集合的特点
- 可以去除重复
- 存取顺序不一致
- 没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取、删除set集合中的元素
Set集合存储数据的两种方式:
- 自然排序:自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
- 比较器排序:创建TreeSet对象的时候传递Comparable的实现类对象,重写compare方法,根据返回值进行排序。
两种方式关于返回值的规则:
- 如果返回值为负数,表示当前存入的元素是较小值,存左边
- 如果返回值为零,表示当前要存入的数据和集合中的有重复了,舍弃掉
- 如果返回值为正数,表示当前存入的元素是较大值,存右边
HashSet集合的特点:
- 底层数据结构是哈希表
- 不能保证存储和取出顺序完全一致
- 没有带索引的方法,所以不能使用普通for循环
- 由于是set集合, 所以元素唯一
- 如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法
哈希表的组成
- JDK8开始后,底层采用数组+链表+红黑树组成。
ArrayList、LinkList、HashSet、LinkHashSet、TreeSet使用规则
- 如果希望元素可以重复,又有索引,索引查询要快?
用ArrayList集合,基于数组的。(用的最多 - 如果希望元素可以重复,又有索引,增删首尾操作快?
用LinkedList集合,基于链表的。 - 如果希望增删改查都快,但是元素不重复、无序、无索引。
用HashSet集合,基于哈希表的。 - 如果希望增删改查都快,但是元素不重复、有序、无索引。
用LinkedHashSet集合,基于哈希表和双链表。 - 如果要对对象进行排序。
用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
Map
- Map集合的特点都是由键决定的。
- Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
- Map集合后面重复的键对应的值会覆盖前面重复键的值。
- Map集合的键值对都可以为null。
Map | ||||
---|---|---|---|---|
HashMap | HashTable | 其他Map | ||
LinkHashMap | Properties | TreeMap |
使用最多的Map集合是HashMap。
Map集合实现类特点
- HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
- LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
- TreeMap:元素按照建是排序,不重复,无索引的,值不做要求。
Map集合的遍历方式有:3种。
- 方式一:键找值的方式遍历:先获取Map集合全部的键,再根据遍历键找值。
Map<String, Integer> buy=new HashMap<>();
buy.put("苹果手机", 2);//添加键值对
buy.put("智能手表", 1);
buy.put("java书", 1);
buy.put("c语言书", 1);
buy.put("西瓜", 2);
//Map集合的遍历
//先把所有键取出来
Set<String> s1=buy.keySet();
//开始根据键找值
for (String key : s1) {
Integer value=buy.get(key);
System.out.println(key+"->>>>"+value);
}
- 方式二:键值对的方式遍历,把“键值对“看成一个整体,难度较大。
// 先获取键值对
Set<Map.Entry<String,Integer>> en=buy.entrySet();
for (Map.Entry<String, Integer> entry : en) {
String key=entry.getKey();
Integer value=entry.getValue();
System.out.println(key+"->>>"+value);
}
- 方式三:JDK 1.8开始之后的新技术:Lambda表达式。
//Lamubda表达式遍历
buy.forEach((k,v)->{
System.out.println(t+"->>>"+v);
}
});
HashMap的特点
- HashMap底层是哈希表结构
- 依赖hashCode和equals方法保证键的唯一
- 如果键要存储的是自定义对象,需要重写hashcode和equals方法
- HashMap跟HashSet底层原理是一模一样的,都是哈希表结构,只是HashMap的每个元素包含两个值而已。
TreeMap的特点
- TreeMap底层是红黑树结构
- 依赖自然排序或者比较器排序,对键进行排序
- 如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象的时候给出比较器排序规则
IO流
- intput,把硬盘文件中的数据读入到内存的过程,称之输入,负责读。
- output,把内存中的数据写出到硬盘文件的过程,称之输出,负责写。
或者
Properties
- Properties是一个Map体系的集合类
- Properties中有跟io相关的方法
- Properties只存储字符串
Properties核心作用:
- Properties代表的是一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去。
- 属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value,后续做系统配置信息的。
反射
- 反射是在运行时获取类的字节码文件对象:然后可以解析类中的全部成分(构造器,属性,方法)。
- 反射的核心思想和关键就是:得到编译以后的class文件对象。
获取Class类的对象的三种方式
- 方式一:Class c1 = Class.forName(“全类名”);
- 方式二:Class c2 = 类名.class
- 方式三:Class c3 = 对象.getClass();
使用反射技术获取构造器对象并使用
- 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。
- Class类中用于获取构造器的方法
- 获取构造器的作用依然是初始化一个对象返回。
利用反射技术获取构造器对象的方式
- getDeclaredConstructors()
- getDeclaredConstructor (Class<?>… parameterTypes)
反射得到的构造器可以做什么?
- 依然是创建对象的
- public newInstance(Object… initargs)
如果是非public的构造器,需要打开权限(暴力反射),然后再创建对象
- setAccessible(boolean)
- 反射可以破坏封装性,私有的也可以执行了。
利用反射技术获取成员变量的方式
获取类中成员变量对象的方法
- getDeclaredFields()
- getDeclaredField (String name)
反射得到成员变量可以做什么?依然是在某个对象中取值和赋值。
- void set(Object obj, Object value):
- Object get(Object obj)
如果某成员变量是非public的,需要打开权限(暴力反射),然后再取值、赋值
- setAccessible(boolean)
利用反射技术获取成员方法对象的方式
获取类中成员方法对象
- getDeclaredMethods()
- getDeclaredMethod (String name, Class<?>… parameterTypes)
反射得到成员方法可以做什么?依然是在某个对象中触发该方法执行。
- Object invoke(Object obj, Object… args)
如果某成员方法是非public的,需要打开权限(暴力反射),然后再触发执行
- setAccessible(boolean)
反射为何可以给约定了泛型的集合存入其他类型的元素?
- 编译成Class文件进入运行阶段的时候,泛型会自动擦除。
- 反射是作用在运行时的技术,此时已经不存在泛型了。
注解
Java 语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注。