java知识点总结ing
一、面向对象基础
0、类和对象
对象:是具体的事物 xiaoming xiaohong 类:是对对象的抽象(抽象 抽出象的部分)Person
先有具体的对象,然后抽象各个对象之间象的部分,归纳出类通过类再认识其他对象。
【生活案例】
类是一个图纸 对象是根据该图纸制造多个实物
类是一个模具 对象是使用模具制造的多个铸件(月饼模子 )
类是上海大众汽车,对象就是大家购买的一辆辆具体上海大众汽车
1、内存分析
(1)程序执行过程
(2)容易造成内存泄漏的操作
2、构造器
3、this
4、static关键字
static修饰的成员变量和方法,从属于类。
普通变量和方法,从属于对象。
① 静态方法不能访问非静态成员【非静态成员必须有具体的类对象才可以访问】
② 非静态方法可以访问静态成员
class TS2{ public int i = 20; private static TS2 ts = new TS2(); // 限制通过new生成类对象 private TS2() { } public static TS2 getTS2() { //static是一定不能省略的,因为不能创建对象,只能由类来访问,所以得设置成静态方法 return ts; } } public class TestStatic2 { public static void main(String[] args) { // TODO Auto-generated method stub // TS2 ts = new TS2(); //error,因为构造方法是private类型的,不能直接通过new构造新对象 TS2 ts0 = TS2.getTS2(); TS2 ts1 = TS2.getTS2(); ts0.i = 99; System.out.printf("%d\n",ts1.i); //99,证明 ts0和ts1是同一个对象 if(ts0 == ts1) { System.out.println("ts0和ts1相等"); //ts0和ts1相等 }else { System.out.println("ts0和ts1相等"); } } }
5、参数值传递
6、package(同一个包里不能有同名的类)
二、面向对象进阶
0、继承使用要点
(1)Java中只有单继承:Java中类没有多继承,接口有多继承。
(2)如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object。
(3)使用ctrl+T方便查看类的继承层次结构。
(4)instanceof:是二元运算符,左边是对象,右边是类;当对象是右面类或子类所创建对象时,返回true,否则返回false。
1.1、方法重载 overload:
指在一个类中,多个方法的方法名相同,但是参数列表不同。参数列表不同指的是参数个数、参数类型或者参数的顺序不同。
【选择困难症】
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1.2、方法重写 overdide:
在Java程序中,类的继承关系可以产生一个子类,子类继承父类,它具备了父类所有的特征,继承了父类所有的方法和变量。
子类可以定义新的特征,当子类需要修改父类的一些方法进行扩展,增大功能,程序设计者常常把这样的一种操作方法称为重写或覆盖。
/** 1>定义:在子类中重新定义父类中已有的方法 2> “==”:具有相同的方法名称、参数列表。 3> “<=”:返回值类型和声明异常类型,子类小于等于父类。
4> “>=”:重写方法时,子类不能使用比父类中被覆盖的方法更严格的访问权限(便于实现多态),也就是说不能低于父类设置的访问权限。 */
2、“==”和equals和hashCode有什么区别
2.1 “==”:代表比较双方是否相同。如果是基本类型则表示值相等,如果是引用类型则表示地址相等即是同一个对象。
2.2 equals:实际是上是Object类中定义的一个方法【public boolean equals(Object obj)】,提供定义 “对象内容相等” 的逻辑。一般都会重写equals方法。
具体来说,如果两个变量是基本数据类型,可以(只能)直接使用==来比较其对应的值是否相等。
(1)如果一个变量指向的数据是对象(引用类型),那么涉及到了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存(栈内存)。
这时候如果用==表示:这两个对象是否指向同一个对象(即指向同一块存储空间——堆内存)。但是如果要比较这两个对象的内存(占内存)是否相等,==运算符就无法实现了。
(2)在没有覆盖equals方法的情况下,equals方法与==运算符一样,比较的是引用。
equals方法的特殊之处在于它可以被覆盖,通过覆盖的方法让它不是比较引用而是比较数据内容,例如String类的equals方法就是用于比较两个独立的对象的内容是否相同,即堆中的内容是否相同。
public static void main(String[] args)
{
String s1=new String("hello");
String s2=new String("hello");
String s3=s1;
System.out.println(s1==s2); //false,说明只能比较堆内存中存储的值
System.out.println(s1==s3); //true
System.out.println(s1.equals(s3)); //true
System.out.println(s1.equals(s2)); //true
}
2.3 hashCode()方法:也是从Object类中继承过来的,也用来鉴定两个对象是否相等。
Object类的hashCode方法返回:对象在内存中的地址转换成的一个int值,如果没有重写hashCode方法,任何对象的hashCode方法都是不相等的。
(1)在HashMap中,由于key是不可以重复的,在判断key是否重复就使用了hashCode方法,并且也用到了equals方法,此处不可以重复指的是equals方法和hashCode方法只要有一个不等就可以了。
(2)一般在覆盖equals方法的同时也要覆盖hashCode方法,否则就会违反Object hashCode的通用约定,从而导致该类无法无所有基于散列值的集合类(HashMap、HashSet和HashTable)结合在一起正常运行。
// 原因:
(1)如果只覆盖了equals方法而没有覆盖hashCode方法,则两个不同的实例 A 和 B,虽然equals结果相等,但是却会有不同的‘HashCode,这样HashMap中会同时存在A和B,而实际上我们需要HashMap里面只能保存其中一个。
比如你只覆盖了equals方法而没有覆盖hashCode方法,那么HashMap在第一步寻找链表的时候会出错,有同样值的两个对象Key1和Key2并不会指向同一个链表或桶,因为你没有提供自己的hashCode方法,那么就会使用Object的hashCode方法,
该方法是根据内存地址来比较两个对象是否一致,由于Key1和Key2有不桶的内存地址,所以会指向不同的链表,这样HashMap会认为key2不存在,虽然我们期望Key1和Key2是同一个对象;
(2)反之如果只覆盖了hashCode方法而没有覆盖equals方法,那么虽然第一步操作会使Key1和Key2找到同一个链表,但是由于equals没有覆盖,那么在遍历链表的元素时,key1.equals(key2)也会失败(事实上Object的equals方法也是比较内存地址),
从而HashMap认为不存在Key2对象,这同样也是不正确的。
3、super
super是直接父类对象的引用。可以通过super来访问父类中被子类覆盖的方法或属性。
(1)每个子类构造方法的第一条语句,都是隐含地调用super()。依次向上追溯每个父类,如果没有这种形式的构造函数,那么在编译的时候会报错。
流程就是:先向上追溯到Object的构造方法 ,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。【静态初始化块调用顺序 同理】
(2)如果显式的写出super();语句,则必须保证该语句是第一条语句,否则会报错。
(3)super();如果不写,则编译器会自动添加,所以此时如果父类没有无参的构造函数就会出错。
(4)既可以显式写super();前提是父类必须有无参的构造函数;也可以显式写super(实参); 前提是父类必须有带参的构造函数。
(5)调用父类的构造函数的语句必须借助于super, 不能直接写父类的类名。
4、封装——使用访问控制符实现
(1)【高内聚、低耦合】:①类的内部操作细节 自己完成,不允许外部干涉;②仅暴漏少量的方法给外部使用,尽量方便外部调用。
(2)
(3)使用细节——类的属性的处理:
1> 一般使用private访问权限
2> 提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的,以提供属性的赋值与独取值操作(注意:Boolean变量的get方法是is 开头的)
3> 一些只用于本类的辅助性方法可以用private修饰,希望其他类调用的方法用public修饰
5、多态
(1)指的是同一个方法调用,由于对象不同可能会有不同的行为。现实生活中,同一个方法,具体实现会完全不同。
(2)多态的要点:
① 多态是方法的多态,不是属性的多态(多态与属性无关)。
② 多态的存在要有3个必要条件(继承 + 方法重写 + 父类引用指向子类对象)
③ 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
public class TestPoly{ //多态必备:继承 + 方法重写 + 父类引用指向子类对象 public static void main(String[] args) { Animal a = new Animal(); AnimalCry(a); Dog d = new Dog(); AnimalCry(d); // 多态 <=> Animal a = new Dog(); Animal c = new Cat(); // 自动向上转型 AnimalCry(c); // c.nianren(); 出错,因为在编译器里认为c 是Animal型 Cat c2 = (Cat)c; //强制向下转型 c2.nianren(); } static void AnimalCry(Animal a) { a.shout(); } } class Animal{ public void shout() { System.out.println("叫了一声"); } } class Dog extends Animal{ public void shout() { System.out.println("汪汪汪"); } } class Cat extends Animal{ public void shout() { System.out.println("喵喵喵"); } public void nianren() { System.out.println("粘人的小猫咪"); } }
【运行结果:】
叫了一声
汪汪汪
喵喵喵
粘人的小猫咪
6、final关键字的作用
(1)修饰变量:被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。
(2)修饰方法:该方法不可以被子类重写。但是可以被重载。
(3)修饰类:修饰的类不能被继承。比如:Math、String等。
7、抽象abstract —— 意义:为子类提供统一的、规范的模板,子类必须实现
(1)有抽象方法的类只能定义为抽象类
(2)抽象类不能实例化,即不能用new来实例化抽象类
(3)抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实例,只能用来被子类调用
(4)抽象类只能被继承
(5)抽象方法必须被子类实现
8、接口 interface
(1)接口就是规范,定义的是一组规则。 里边所有的方法都是抽象方法,全面专业的实现了:规范和具体实现的分离。
(2)定义接口的详细说明:
(3)要点
(4)为什么需要接口?接口和抽象类的区别?
(5)接口相关规则
9、内部类 —— 将一个类定义置入另一个类定义中就叫作“内部类” outer$inner.class
(1)内部类分类:
成员内部类:静态内部类 + 非静态内部类
(局部)方法内部类
匿名内部类
(2)类中定义的内部类特点
(3)匿名内部类
public class TestInner { public static void main(String[] args) { // 创建内部类对象 Outer.Inner inner = new Outer().new Inner(); inner.show(); } } class Outer{ private int age = 10; public void testOuter() { System.out.println("Outer.testOuter!"); } class Inner{ int age = 20; public void show() { int age = 30; System.out.println("外部类的成员变量age:"+Outer.this.age); System.out.println("内部类的成员变量age:"+this.age); System.out.println("局部变量age:"+age); } } } //运行结果 外部类的成员变量age:10 内部类的成员变量age:20 局部变量age:30
10、String类 —— 不可变字符序列
11、数组
(1)相同类型数据的集合。
(2)foreach循环:专门用于读取数组或集合中所有的元素,即对数组进行遍历。
拓展:
1、集合框架的线程安全性
- A,Vector相当于一个线程安全的List
- B,HashMap是非线程安全的,其对应的线程安全类是HashTable
- C,Arraylist是非线程安全的,其对应的线程安全类是Vector
- D,StringBuffer是线程安全的,相当于一个线程安全的StringBuilder
- E,Properties实现了Map接口,是线程安全的
2、基于分代的垃圾收集算法
设计思路:把对象按照寿命长短来分组,分为年轻代和年老代,新创建的对象被分在年轻代,如果对象经过几次回收后仍然存活,那么再把这个对象划分到年老代。
年老代的收集频率不像年轻代那么频繁,这样就减少了每次垃圾回收时所要扫描的对象的数量,从而提高了垃圾回收效率。
把堆划分为若干个子堆,每个堆对应一个年龄代:
// JVM将整个堆划分为Young区、Old区和Perm区,存放不同年龄的对象,这个三个区存放的对象有如下区别: Young区:又分为Eden区和两个Survivor区,其中所有新创建的对象都在Eden区,当Eden区满后会触发minor GC将Eden区仍然存活的对象复制到其中一个Survivor区中,
另外一个Survivor区中的存活对象也复制到这个Survivor中,以保证始终有一个Survivor区是空的。 Old区:存放的是Young区的Survivor满后触发的minor GC后仍然存活的对象,当Eden区满后会将对象存放到Survivor区中,如果Survivor区仍然存不下这些对象,
GC收集器会将这些对象直接存到Old区,如果在Survivor区中的对象足够老,也直接存放到Old区,如果Old区也满了,将会触发Full GC,回收整个堆内存。 Perm区:存放的主要是类的Class对象,Perm区的垃圾回收也是由Full GC触发的。
3、finally块中的代码什么被执行?
若try{}里面有一个return语句,finally块里的代码也是在return之前执行的。
如果try-finally或者catch-finally中都有return,那么finally块中的return语句会覆盖别的return语句,最终返回到调用者的是finally中的return的值。
4、运行时异常和普通异常区别
Java提供了两种错误的异常类,分别为Error和Exception,且他们有共同的父类Throwable。
Error表示程序在运行期间出现了非常严重的错误,并且错误是不可恢复的。例如OutMemoryError、ThreadDeath、方法调用栈溢出 等都属于错误。当这些异常发生时,JVM一般会选择将线程终止。
Exception表示可恢复的异常,是编译器可以捕捉到的。它有两种类型:检查异常和运行时异常
- 检查异常:最常见的IO异常和SQ异常,这种异常都发生在编译阶段,编译器强制程序去捕获此类型的异常,果没有try……catch也没有throws抛出,编译是通不过的。
- 运行时异常:编译器没有强制对其进行捕获处理,如果不对这种异常进行处理,当出现这一种异常时,会有JVM处理,例如NullPointException空指针异常,ClassCastException类转换异常,ArrayIndexOutBoundsException数组越界异常,算术异常等。
5、多线程同步的实现方法
1、synchronize关键字
在Java中,每个对象都与一个对象锁与之相关联,该锁表明对象在任何时候只允许被一个线程所拥有,当一个线程调用对象的一个synchronize代码时,需要先获取这个锁,然后去执行相应的代码,执行结束后吗,释放锁。
2、wait方法与notify方法
线程调用对象的wait方法,进入等待状态,释放对象锁,并且可以调用notify方法通知正在等待的其他的线程。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class WaitAndNotify { class TestWait extends Thread { public void run() { test("wait线程"); } } class TestNotify extends Thread { public void run() { test("notify线程"); } } public void exe() { TestWait tw = new TestWait(); TestNotify tn = new TestNotify(); tn.start(); tw.start(); } // 监视器 public synchronized void test(String str) { for (int i = 0; i < 10; i++) { if (i == 5) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { notify(); } System.out.println(str + "-------------" + i); } } public static void main(String[] args) { new WaitAndNotify().exe(); } }
3、Lock
Lock接口和它的一个实现类ReentrantLock(重入锁)。
lock(): 以阻塞的方式获取锁,即若获取到锁,立即返回,如果别的线程持有锁,当前线程等待,直到获取锁后返回。
tryLock(): 以非阻塞的方式获取锁,只是尝试性的去获取一下锁,如果获取到锁,返回true,否则返回false
6、sleep方法与wait方法的区别
sleep和wait均是使线程暂停执行的方法,区别在于:
1、原理不同。sleep方法是Thread类的静态方法,是线程用来控制自身流程的,它会使线程暂停执行一段时间,而把执行机会让给其他线程,等到计时时间一到,此线程就会自动“苏醒”。wait方法是Object类的方法,用于线程间的通信,这个方法会使得拥有该对象锁的进程等待,直到其他线程调用notify方法时才醒来。
2、对锁的处理机制不同。由于sleep方法的主要作用是让线程暂停执行一段时间,时间一到则自动恢复,不涉及线程间的通信,因此调用sleep方法不会释放锁。而调用wait方法后,线程会释放掉它所占用的锁,从而使线程所在对象中其他synchronize数据可被别的线程使用。
3、使用区域不同。wait方法必须放在同步控制方法或同步语句块中使用,而sleep()可以在任何地方使用。
7、synchronize与Lock区别
1、用法不同。synchronize既可以加在方法上,也可以加载特定代码中,括号表示需要锁的对象。
Lock需要显示的指定起始位置和终止位置。synchronize是托管给JVM执行的,Lock的锁定是通过代码实现的。
2、锁机制不一样。synchronize获取锁和释放锁的方式都在在块结构中,当获取多个锁时,必须以相反的顺序释放,并且是自动释放的。
Lock则需要开发人员手动去释放,并且必须在finally块中释放,否则会引起死锁问题的发生。
8、join()方法的作用
join方法的作用是:让调用该方法的线程在执行完run()方法后,再执行join方法后面的代码(插队)。例如可以通过线程A的join方法来等待线程A的结束。
9、Http中get与post方法的区别
get方法的作用主要用来获取服务器端资源信息,如同数据库中查询操作一样,不会影响到资源本身的状态。
post方法提供了比get方法更加强大的功能,它除了能够从服务器端获取资源外,同时还可以向服务器上传数据。
-—— get方法主要用来从服务器上获取数据,也可以像服务器上传数据,但是不建议,原因如下:
(1)采用get方法向服务器上传数据时,一般将数据添加到URL后面,并且二者用“?”连接,各个变量之间用“&”连接。由于URL对长度的限制,此种方法能上传的数据量非常小。
post方法传递数据通过http请求的附件进行的,传送的数据量更大。
(2)get方法上传数据被彻底暴露在url中,本身存在安全隐患。
10、Servlet中forward和redirect有什么区别
1、forward是服务器内部的重定向(转发),服务器直接访问目标地址的url,把那个url的响应读取过来,而客户端并不知道。
(1)在客户端浏览器的地址栏不会显示转向后的地址。
(2)是一次请求,由于在整个定向的过程中用的是同一个Request,因此forward会将Request的信息带到被定向的jsp或Servlet中使用,资源可以共享。
(3)不能访问外部服务器(站点)资源
2、redirect是客户端的重定向,是完全的跳转,即客户端浏览器会获取到跳转后的地址,然后重新发送请求,比forward多一次网络请求。
(1)在客户端浏览器的地址栏显示转向后的地址。
(2)是两次请求,不能使用request对象共享资源。
(3)可以访问其他服务器(站点)资源
11、getparameter和getattribute的区别
(1)作用范围
request.getParameter()方法:传递的数据,会从Web客户端传到Web服务器端,代表HTTP请求数据,取得通过类似post,get等方式传入的数据(String类型)。
request.setAttribute()和getAttribute()方法:传递的数据只会存在于Web容器内部,在具有转发关系的Web组件之间共享,仅仅是请求处理阶段。这两个方法能够设置Object类型的共享数据。
(2)返回类型
- request.getAttribute()方法返回request、sessiont范围内存在的对象,
- request.getParameter()方法是获取http提交过来的数据(String)。
12、JDK1.8中的ConcurrentHashMap
与jdk1.7相比,jdk1.8中主要做了2方面的改进:
改进一:取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。
改进二:将原来table数组+单链表的数据结构,改为table数组+单链表+红黑树结构。
采用单链表方式,查询某个节点的时间复杂度为O(n),因此,对于个数超过8的列表,jdk1.8中采用了红黑树的结构,查询时间复杂度可以降低到O(logN)。
HashEntry 类的 value 域被声明为 Volatile 型,Java 的内存模型可以保证:某个写线程对 value 域的写入马上可以被后续的某个读线程“看”到。
在 ConcurrentHashMap 中,不允许用 null作为键和值,当读线程读到某个 HashEntry 的 value 域的值为 null 时,便知道产生了冲突——发生了重排序现象( 对链表的结构性修改都可能会导致value为null ),
需要加锁后重新读入这个 value 值。这些特性互相配合,使得读线程即使在不加锁状态下,也能正确访问 ConcurrentHashMap。