java基础学习笔记
基础知识
1.java的可移植性原理:依赖不同平台的jvm,所有的源代码(.java)经过 javac 编译成字节码文件(.class)就可以在不同平台的 JVM 上解释执行。
2.public class 和 class 的区别:一个java文件只能有一个 public 声明的 class ,并且该类名和文件名称相同,可以同时存在多个 class 类,经过编译生成多个 .class 文件,执行的时候一定要执行已经生成的 .class 文件。
3.java 数据类型的划分:8 个基本数据类型{boolean,char,byte,short,int,long,float,double}、3 个引用数据类型{class,interface,array}
4.方法:一段可以重复调用的代码段。方法的重载:根据方法的签名来判断方法重载。方法签名:参数个数和参数类型、参数顺序。
5.数组:数组是引用数据类型,所以一定要划分出内存:栈和堆两块内存。数组的声明定义、初始化和简单操作,jdk 提供的相关操作方法:java.util.Array.sort(数组名称)、System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)数组拷贝。
6.length、length()、size():length是数组的一个属性,length()是字符串的一个方法,size()是泛型集合的一个方法。
7.JDK、JRE、JVM三者间的关系:金字塔结构 JDK=JRE+JVM+Java程序
面向对象基础
1.类和对象的概念:类是共性的抽象描述,对象是类的具体表现。
2.面向对象的三大特性:封装、继承、多态。
3.构造方法:构造方法的主要作用就是为对象的属性初始化,默认会为类创建一个无参构造方法,一旦手动创建非无参构造方法,则不会再创建默认的无参构造方法,这时需要手动创建无参构造方法。
4.匿名对象:没有对应的栈内存空间,仅使用一次就等待被垃圾回收。
5.String类:
1).变量的初始化的两种方式{直接赋值、调用构造方法 new String(“字符串”)}.
直接赋值会开辟一个栈内存空间,如果在字符串常量池中不存在该字符串引用地址,
则将会开辟一个堆内存空间创建该字符串对象并返回该字符串引用地址存放到字符串常量池(在本地方法栈中)中并使得引用变量指向他,而且以后若有相同的内容将不再创建
字符串对象及字符串常量池引用而是直接将引用变量指向常量池中的该字符串的引用,但使用构造方法,则会开辟三个或两个内存空间,一个栈内存空间和两个或一个堆内存空间,使用构造
方法的形式创建的对象将直接放置到堆中,每调用一次就会创建一个新的对象,而构造方法的参数也是一个string对象,该字符串也是需要在堆内存中创建存储的,若已存在则可不需要创建。
<strong style="color:red;">因此,直接赋值方式可能会只创建一个栈内存空间(所创建的字符串对象的引用在常量池中已经存在),也可能创建三个内存空间(一个栈内存空间、一个字符串常量池栈空间(方法栈空间)
和一个堆内存空间),但构造方法的方式肯定是创建两个或三个内存空间(一个栈内存空间和一个或两个堆内存空间)。</strong>
String s = new String("abc"); 创建了几个对象的问题:
当 “abc” 在 字符串常量池中已经存在的时候,创建了 一个 对象, 是在构造方法返回的时候创建的;
当 “abc” 在 字符串常量池中不存在的时候, 创建了 两个 对象, 一个是在构造方法返回的时候创建的,另一个是 创建 字符串 "abc" 的,因为 new String() 这个构造方法的参数 也是一个字符串。
String s = "abc"; 和 String s = "ab" + "cd";
都只创建了一个对象,因为这种方式在编译期 s 的值 就已经确定了,常量的值 在 编译期 就已经确定了, 而 只有使用引号包含的文本方式创建的String对象之间使用 “+” 连接产生的新对象才会被加入字符串常量池中。
对于 String s = "abc"; 就是创建的 “abc” 这一个 对象,
对于 String s = "ab" + "cd"; 其实质 就相当于 String s = "abcd"; 同理,String s = "a" + “b” + "c" + "d"; 也只创建了一个对象。
参考资料:
java中String类两种初始化的区别、Java中两种字符串初始化方法的区别、
Java虚拟机 运行时数据区、触摸java常量池
2).Java中的String为什么是不可变的.
在Java中String类其实就是对字符数组的封装,而String类的变量都是 private final ,外部不能修改,这样就是,String变量一旦初始化就不能修改;而String类提供了一些修改替换等方法其实质是返回了新的String对象并赋值。
但是可以通过反射的机制来改变String对象的值,因为String类的 value 属性 是char 类型数组的引用,可以通过反射获取 String的 value 字段并改变他的访问权限,然后获取value的值在进行改变,这样是可以达到目标的。
参考资料:
Java中的String为什么是不可变的? – String源码分析、Java中的String为什么是不可变的? — String源码分析
3).== 和 equals() 的区别:
== 是值比较, equals 是引用比较。 == 对于基本数据类型来说就是比较他们的值,而对于引用数据类型来说是比较他们存放的引用地址;equals是用于引用类型的值比较。
值得注意的是:
<strong style="color:red;">在Java中,Object对象的equals方法默认使用了= =操作符,所以如果你自创的类没有覆盖equals方法,那你的类使用equals和使用==会得到同样的结果。</strong>
参考资料:
Java中equals和==的区别、equals与”==”的区别
4).string类的相关方法使用:
|- 与字符相关:toCharArray()、charAt(int ind)、new String(char c[])、new String(char c[],int offset,int length)
|- 与字节相关:getBytes()、new String(byte b[])、new String(byte b[],int offset,int length)
|- 拆份:public String[] split(String regex)
|- 替换:public String replaceAll(String org,String newc)、public String replaceFirst(String org,String newc)
|- 截取:public String substring(int offset,int length) 、public String substring(int offset)
|- 其他操作:length()、toUpperCase()、toLowerCase()、trim()、equalsIgnoreCase()、
6.java中关于 值传递 和 引用传递的争论:“《Core Java》的作者,以及JAVA的创造者James Gosling都认为当一个对象或引用类型变量被当作参数传递时,也是值传递”
参见:Java值传递和引用传递之我见、java到底是值传递还是引用传递?
为什么 Java 只有值传递,但 C# 既有值传递,又有引用传递,这种语言设计有哪些好处?
7.static、this关键字:
static关键字:
static所修饰的方法、字段、初始化块等是属于类本身的而不是对象实例,“static的真正作用是用于就是用于区分Field、方法、内部类、初始化块这四种成员到底属于类本身还是属于实例。”
this关键字:
1)Java提供了一个this关键字,this关键字总是指向调用该方法的对象。
根据this出现位置的不同,有两种情形:
(1)在构造器中引用该构造器正在初始化的对象;
(2)在方法中引用调用该方法的对象。
2)this关键字最大的作用就是让类中一个方法,访问该类里的另一方法或Field。
3)this可以代表任何对象,当this出现在某个方法体中时,它所代表的对象是不确定的,但是它的类型是确定的,它所代表的对象只能是当前类;只有当这个方法被调用时,它所代表的对象才被确定下来:谁调用这个方法,this就代表谁。
4)static修饰的方法可以直接使用类来调用方法。如果在static修饰的方法中使用this关键字,则这个关键字就无法指向合适的对象。所以,static修饰的方法中不能使用this引用。
类的初始化过程(执行顺序):
在不涉及继承关系的时候的执行顺序:
(静态变量、惊天初始化块)>(变量、初始化块)>构造器
在设计有继承关系时候的执行顺序:
(父类静态变量、静态初始化块)>(子类静态变量、静态初始化块)>(父类变量、初始化块)> 父类构造器 >(子类变量、子类初始化块)> 子类构造器
并不是父类完全初始化结束才进行子类初始化,而是子类的静态变量和静态初始化块在父类变量和初始化块之前初始化。而(静态)变量和(静态)初始化块之间的顺序是由他们在类声明时出现的先后顺序决定
9.内部类:是指在一个外部类的内部再定义一个类。内部类作为外部类的一个成员,并且依附于外部类而存在的。
Java中非静态内部类对象的创建要依赖其外部类对象
即:
new Outer().new Inner();
使用内部类的原因:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。
其实使用内部类最大的优点就在于它能够非常好的解决多重继承的问题,但是如果我们不需要解决多重继承问题,那么我们自然可以使用其他的编码方式,但是使用内部类还能够为我们带来如下特性(摘自《Think in java》):
1、内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。
2、在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
3、创建内部类对象的时刻并不依赖于外围类对象的创建。
4、内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
5、内部类提供了更好的封装,除了该外围类,其他类都不能访问。
当我们在创建一个内部类的时候,它无形中就与外围类有了一种联系,依赖于这种联系,它可以无限制地访问外围类的元素。
1 | public class OuterClass { |
内部类是个编译时的概念,一旦编译成功后,它就与外围类属于两个完全不同的类(当然他们之间还是有联系的)。对于一个名为OuterClass的外围类和一个名为InnerClass的内部类,在编译成功后,会出现这样两个class文件:OuterClass.class和OuterClass$InnerClass.class。
在Java中内部类主要分为成员内部类、局部内部类、匿名内部类、静态内部类.具体了解参考
10.类的继承:方便地实现了功能的扩充,在java中只允许单继承;子类能直接继承父类所有的非私有操作,而只能隐式地继承所有的私有操作。
11.重载 和 覆写 的区别:
重载:根据方法签名来判断方法重载
是同一个类中,相同的方法名称,不同的参数列表,以实现不同的功能。
覆写:在继承关系中体现
是子类和父类之间,相同的方法名称并且方法的参数列表一样,在不同类中实现不同的方法。但子类中的访问权限不能比父类中的严格。
12.final 关键字:修饰的类怒能再被继承,修饰的方法不能被子类覆写,修饰的变量表示常量。
13.抽象类: 包含一个抽象方法的类,抽象类必须使用abstract关键字声明,所有的抽象类不能直接实例化,只能被子类继承并覆写所有的抽象方法。
14.接口: 抽象方法和全局常量的集合,使用 interface 关键字进行声明,通过 implements 关键字被子类实现,一个子类可以实现多个接口,一个接口也可以继承多个接口。
15.包装类、自动装箱、自动拆箱:包装类是对基本数据类型的处理封装并包含了许多与基本数据先关的操作的封装;装箱:基本数据类型转换为包装类就是装箱,反之就是拆箱。
面向对象高级
1.抽象类 和 接口 的区别:
共同点:
都不能实例化,都是抽象类型(抽象层次不同,抽象类是对类抽象,接口是对行为抽象;
抽象类是对有相似特点的类的一种继承关系的体现,而接口的子类之间可以毫无关系
从设计层次来看,抽象类是自底向上抽象,而接口是自顶向下设计的),
都是实现多态的一种机制,子类都需要实现定义的所有的抽象方法,
不同点:
抽象类:
1.能有默认的方法实现
2.可以存在构造器
3.可以由 public、protected、default来修饰
4.可以存在main方法,并且可以运行
5.可以继承一个类或实现多个接口
6.新增方法可能不需要更改实现类
接口:
1.不能有任何方法实现
2.不能有构造器
3.只能由默认的public来修饰,不能用其他来修饰
4.不能存在main方法
5.只能继承一个或多个其他接口
6.新增方法一定要更改实现类
综述:最重要的三方面:方法有默认实现,多继承实现,新增方法对实现类的影响
参考资料:java提高篇(四)—–抽象类与接口、Java抽象类与接口的区别
2.java 的多态性: 重载、覆写、向上转型
在面向对象中,多态分为静态多态和动态多态,静态多态是指编译期就已经确定了调用目标,主要是指重载;而动态多态是指在程序运行期才能确定调用目标,是通过动态绑定来实现的,主要是指覆写(通过继承和接口方式)。
参考资料:java提高篇(四)—–理解java的三大特性之多态、Java多态性详解——父类引用子类对象、
深入理解java多态性
3.单例设计模式:
懒汉式,线程不安全
1 | public class Singleton{ |
懒汉式,线程安全
1 | public class Singleton{ |
双重检验锁
1 | public class Singleton{ |
参考资料:如何正确地写出单例模式、Java设计模式—单例模式、
设计模式-单例模式
4.匿名内部类:是没有名字的内部类,正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。
使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。
由于匿名内部类不能是抽象类,所以它必须要实现它的抽象父类或者接口里面所有的抽象方法。
注意事项
在使用匿名内部类的过程中,我们需要注意如下几点:
1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
2、匿名内部类中是不能定义构造函数的。
3、匿名内部类中不能存在任何的静态成员变量和静态方法。
4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
6、 我们给匿名内部类传递参数的时候,若该形参在内部类中需要被使用,那么该形参必须要为final。也就是说:当所在的方法的形参需要被内部类里面使用时,该形参必须为final。
参考资料:java提高篇(十)—–详解匿名内部类
5.泛型:
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
参考资料:Java 理论和实践: 了解泛型、Java总结篇系列:Java泛型、
java 泛型中 T 和 问号(通配符)的区别、Java泛型:类型擦除、
Java 提高篇(六) — 泛型
6.里氏代换原则:
里氏代换原则(LiskovSubstitution Principle,简称LSP)说的是:一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能够察觉出基类对象
通俗点:里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。
里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
参考资料:里氏代换原则(LSP)、面向对象设计原则之里氏代换原则、
equals 与里氏代换原则、设计模式六大原则(2):里氏替换原则
关于面试,相关资料推荐
Java面试题全集(上)
Java面试题全集(中)
Java面试题全集(下)
115个Java面试题和答案——终极列表(上)
115个Java面试题和答案——终极列表(下)
最近5年133个Java面试问题列表
40个Java集合面试问题和答案