马士兵的
内存解析:
基本类型占一块内存,引用类型占两块内存。
所有new出来的东西都在堆内存(heap segment)里面。 堆内存:运行期间动态分配内存,因为事先不知道分配多大,只有在运行(而不是编译)期间才知道分配多大。 方法只是一段代码(在 code segment里面),它只有被调用的时候才会占用内存。
引用 — 一小块内存指向一大块内存。 局部变量(c1)分配在栈内存(stack segment)(C c1 = new C();)成员变量是在堆中分配的,而且只有new出来的时候才会在堆中分配。
构造方法:
new的时候实际上去调用构造方法,顾名思义,把类构造成一个对象。必须和类同名并且没有返回值(void也不行)。
没有显示定义构造方法,编译器会自动定义一个空的。Point(){}; 一旦你显示定义了,编译器就不给你加默认的了。
分析:
方法调用完之后,栈内存为它分配的空间全部消失。— 只剩下一个引用(地址)指向堆中一个对象。
public class Test要和java文件名Test同名。
static声明的变量分配在data segment中(数据区)System.out out是静态的对象(PrintStream类)
字符串常量也在数据区。
权限:
很简单:protected 同一个包和子类能访问,。default是同一个包能访问。(记住”protected同包同子类,默认的同包” 就完了)
private和public就更简单了,private类内部才行,public任何地儿都行。
spuer引用父类对象,因为new子类对象的时候里面会有一个父类对象,那如何引用呢,就用super。
new一个对象出来的时候,它会有一个this引用指向自身,如果这个对象是子类对象那还会有一个super引用指向当前对象的父对象,
继承中的构造方法:
要构一个子类对象,那必须要先构一个父类对象。否则子类从哪来。所以子类构造方法中默认有一个super();调用父类无参的构造方法。
那Object类是所有的父类(默认class Person extends Object),它一定有一个空的构造方法,把它先造出来。
static:
Java里面static一般用来修饰成员变量或函数。但有一种特殊用法是用static修饰内部类
普通类是不允许声明为静态的,只有内部类才可以,详情请问内部类的讲解
Object类:
Object
clone() –
finalize() – 有点类似于C++的析构函数 垃圾回收器回收前,会调用finalize方法。— 用的比较少,不必纠结。
getClass() — 可以认为是编译好的class文件,返回值是一个Class对象….小写的class是关键字,大写的是一个类
int hashCode() — 返回对象的哈希码。举例:对象可以根据哈希码很快找到自己在内存中的位置。
wait、notify、notifyAll – 线程间通信会涉及到
String toString() — 返回代表一个对象的字符串。 看下图第二条。。
Object的toString() 默认的实现是: 后半截是它的16进制的哈希编码。建议所有子类都重写
int hashCode() — 站在JVM的角度看,在内存里面有好多个对象,一个程序运行的时候会有很多对象在内存里分配,那JVM运行的时候就要找到这些个对象的地址,怎么找呢???
它有一张表来记录每个对象在什么位置上,而这个表一般是用哈希编码来记录的,每个对象都有自己独一无二的哈希码,根据这个哈希码就能找到对应的对象。但是JAVA对哈希码的实现有点问题,不同的俩对象的哈希码可能是相同的。讲Map的时候再说。
equals() – 重写父类Object如下:string也重写了,比较的是字符序列。。
Object类的一些方法:
public class T1 {
public static void main(String[] args){
Cat cat1 = new Cat(1,"y",10);
Cat cat2 = cat1; //地址一样,肯定equals
Cat cat3 = new Cat(1,"y",10);
System.out.println(cat1); //这里会自动调用cat的toString()方法。
// System.out.println(cat1.equals(cat3));
// testString();
}
/**
* 用下string提供的方法
* 1、String的toString方法是return this
* 2、请熟练写出string的equals
*/
public static void testString(){
String s1 = "abc";
String s2 = null;
System.out.println(s1);
System.out.println(s1 == s2); //和null比地址,结果是false
System.out.println(s1.equals(s2)); //object比较的是地址(也就是引用)
}
}
class StringByMyself{
private char value[]; //假设这就是string的value
//String的equals
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
/**
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}*/
return false;
}
}
/**
* 用一个cat 重写toString 和 equals
*/
class Cat{
private int id ;
private String color;
private int weight;
public Cat(int id, String color, int weight) {
this.id = id;
this.color = color;
this.weight = weight;
}
@Override
public String toString() {
System.out.println(super.toString()); //看看地址是什么
// return super.toString(); IDEA默认生成的toString,这个不怎么好,自己写一个
return "id是" + this.id +" 颜色是"+this.color +" 体重是"+this.weight;
}
@Override
public boolean equals(Object obj) {
// return super.equals(obj); IDEA默认生成的toString,这个也不怎么好,自己写一个
//模仿string的写一个
if (this == obj) { //1、先比较地址,相同肯定true,不同肯定false
return true;
}
//2、再比较值,逐个比较属性值
if(obj instanceof Cat){
Cat anotherCat = (Cat)obj;
if(anotherCat.id == this.id &&
anotherCat.color == this.color &&
anotherCat.weight == this.weight){
return true;
}
}
return false;
}
}
常用类
String
空的构造方法出来的是一个空串,不是空值。
要改变编码格式 的时候会用到它的一个构造方法如下。
传入一个字节数组,并指定编码格式。。这个例子可以看慕课网的IO那一节。
public String(byte bytes[], String charsetName){...//此处省略}
小例子:
/**
* 指定字符串在原串中出现了几次。
*思路:indexOf找到了就截取掉,然后再找
*/
public int findSunStr(String sToFind,String s){
int count = 0;
int index = -1;
while ((index = s.indexOf(sToFind)) != -1){
s = s.substring(index + sToFind.length());
count++;
}
System.out.println(count);
return count;
}
File代表文件或者路径名。new File()不表示在硬盘中就有一个文件了,而是内存中有一个对象而已
递归列出目录结构:直接f.listFiles()就行,如果isDirectory()再递归一下就行。
枚举 – 某一些类型必须只能取 某些值之一。否则报错
一扇门只能你和你老婆打开,你是1,你老婆是-1 。其余数字进来就抛异常。但是别人也可以把数字随便赋值。
与其在程序中限制,不如在编译的时候就限制好,所以枚举出现了。
再举例:设计一个游戏,只能规定上下左右四个方向,如果用int (1,2,3,4)表示可以是可以,但是如果写成5,在编译期间就发现不出来。还不如用枚举enum
注意是定义一个枚举类型,而不是一个变量。
高琪的
内存
属性的初始化:
八种基本类型,数字占了六种默认值是0,小数是0.0,boolean默认是false其实也是0,char:\u0000,其实也是0。
除了这八种,其余默认都是null。
return作用:方法返回值 和 结束方法
变量的作用域:
类里面,方法外面:成员变量。系统默认初始化,
方法里面:局部变量,这里需要手动初始化,否则使用的时候编译报错,不使用编译不报错。
方法区在堆里面,是堆的一部分。如下图所示:
记住一句话:操作对象就是操作它的地址。。
Student student = new Student();
Student -- JVM会去classpath路径下找这个类,找到了之后就用类加载器classloader加载,加载后,在方法区中就有了Student类的信息!
student --- 局部变量分配在栈内存
new Student() --- 会实例化一个对象在堆内存
= --- 赋值 这一步 就是把这个对象的地址(一个16进制的数)的值 赋给 student。
Student s2 = new Student(); 下次看Student 这个类有了,就不会再去加载了。
GC垃圾回收机制
什么时候回收,如何回收。JVM已经有算法实现。
C++ : 自己回收 ,餐馆吃完饭,客人自己收。
JAVA:自己回收,餐馆吃完饭服务员回收垃圾(收盘子)
final
内部类
只供当前类使用,内部类可以直接访问外部类私有属性,但是外部类不可直接访问,看下面图片讲解。
非静态的(记住两行红字即可):
静态的(记住两行红字即可):
/**
* 测试内部类的使用
* @author dell
*
*/
public class Outer {
public static void main(String[] args) {
Face f = new Face();
Face.Nose n = f.new Nose(); //这样new才可以,必须先要有外部对象,才能有内部类对象
n.breath();
Face.Ear e = new Face.Ear(); //
e.listen();
}
}
class Face {
int type;
String shape="瓜子脸";
static String color="红润";
//这是非静态内部类
class Nose {
//这个方法不能设置为static,因为非静态内部类是从属于外部类对象的,是从属于一个对象的
void breath(){
System.out.println(shape); //内部类可以直接访问外部的成员变量
System.out.println(Face.this.type);
System.out.println("呼吸!");
}
}
//这是静态内部类,可以看成是外部类的一个静态成员,它是从属于类的。不是对象的
static class Ear {
void listen(){
System.out.println(color); //不能直接访问外部类的普通属性,但可以使用静态属性
System.out.println("我在听!");
}
}
}
内部类分类:
1、成员内部类(当成外部类的一个成员)(静态和非静态,成员也有普通成员和静态成员),
重点看看这个
2、匿名内部类
启动一个线程就是。。new Thread( ).start():
你也可以如下创建一个Thread的匿名子类:
Thread thread = new Thread(){
public void run(){
System.out.println("Thread Running");
}
};
thread.start();
多态
public class HttpServlet {
public void service(){
System.out.println("HttpServlet.service()");
this.doGet(); //隐形的写了 this.doGet()。。写doGet()也可以。
}
public void doGet(){
System.out.println("HttpServlet.doGet()");
}
}
public class MyServlet extends HttpServlet {
public void doGet(){
System.out.println("MyServlet.doGet()");
}
}
public class Test {
public static void main(String[] args) {
HttpServlet s = new MyServlet();
s.service(); //这里的service是调用的是子类的doGet 。
}
}
为啥调的是子类的?? 分析内存:
doGet()和 service()都有两个隐形的this,和super。这里的this指的就是子类。
只需要知道,里面的两个this指的都是最外面的对象也就是子类。所以调的是子类。
数组
数组也是对象,引用类型。除了那四类八种基本类型,其他的都是引用类型,包括数组。
数组里面元素的初始化规则 和 成员变量的初始化规则一样。
操作对象就是操作它的引用 很多类的底层其实都是数组。
String (string,stringbuffer,stringbuilder是数组的典型运用)
在Java中String类其实就是对字符数组的封装
《java中的String为什么是不可变的? – String源码分析》
博客解释:http://blog.csdn.net/zhangjg_blog/article/details/18319521
《 为什么String要设计成不可变的?》
博客解释:http://blog.csdn.net/renfufei/article/details/16808775
数组看完就可以看string源码了,因为底层就是操作数组
final可以修饰变量,方法,类。String类无法被继承
== 比较的是地址,你用一个类的toString打印出来看看就知道了
//初始化这个字符数组后不能再初始化,只能给value赋一次值
//注意:这个char是可以修改的,但是string没有提供暴露方法
//final只是引用的值不可变(),但是指向的对象的值可以变
private final char value[];
//构造方法
public String() {
this.value = new char[0]; //老的JDK,把长度为0的数组对象复制给value
this.value = "".value;
}
//String: 不可变的字符序列。。。 下面分析下源码
public class TestString {
public static void main(String[] args){
String s1 = "ddd";
String s2 = "ddd";
System.out.println(s1 == s2); //常量池是放在方法区的,这里s1和s2指向是同一个常量,所以地址也是一样的
System.out.println(s1.replace("*", "a"));
}
//string类的一个成员变量, 一个不可变(final)的字符数组。什么意思,就是只能给value赋一次值。
//例如 A a = new A();栈内存的a只能赋值一次,a只能指向一个对象而不能指向堆中新的对象,但是a指向的对象的值是可以改变的。
//所以final仅仅是a的值不能变,但是a指向的对象值是可以变的。
// 这里的value初始化一个数组之后,不能再初始化别的数组。数组里面的值也不可变,因为没有提供可变的方法,value是private的,不让你改
//也没有提供暴露的接口。String有getChar()方法,但是没有setChar().如果有setChar方法就可以改value了。虽然value是final的。
//这就是为什么属性私有的原因,我想让你操作就提供set,不想让操作就不提供。这是JAVA封装的思想。
private final char[] value;
private int hash; // Default to 0
//重载的构造函数 特别多
public TestString() {
this.value = new char[0];
}
/*
public TestString(String original) {
this.value = original.value; //string的属性都是private的,只能通过方法去改
this.hash = original.hash;
}*/
public TestString(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
public int length() {
return value.length;
}
public boolean isEmpty() {
return value.length == 0;
}
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
/*
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}*/
public int indexOf(int ch) { //返回字符所在索引位置,找不到就返回-1
return indexOf(ch, 0);
}
//注意,返回的是一个新字符串 new string
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return "";
/*return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen); //这里构造一个新的string,如下*/
}
/*
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count); 还是用到了数组的拷贝
}*/
//常用的方法还有
// public String replace(char oldChar, char newChar) {}
// public String[] split(String regex, int limit) {}
//public String trim() {.../省略} **去除首位空格**,中间的不去
public char[] toCharArray() { //返回一个新的字符数组,这个可以改。 老的是final的,不能改
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
//Cannot use Arrays.copyOf because of class initialization order issues
//这段注释是什么意思? 解释如下:
作者注释写的很清楚了, 由于类初始化顺序的问题。
虽然String 和Arrays 都属性rt.jar中的类,但是BootstrapClassloader 在加载这两个类的顺序是不同的。
所以当String.class被加载进内存的时候,Arrays此时没有被加载,所以直接使用肯定会抛异常。而System.arrayCopy是使用native代码,则不会有这个问题。
另外你说的把代码换掉以后也能运行, 这时候 jvm已经加载完了所有的系统类, 所以你才会看到也能正常运行。
建议楼主去了解一下jvm 类加载器方面的知识 就明白了
}
Object的toString
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode()); 哈希码根据对象在内存中的位置生成,唯一不重复!!
}
String的toString
public String toString() {
return this; //返回自身
}
//这样拼字符串会产生1002个对象,很浪费空间。。尽量避免这样的代码
String gh = new String("a"); //这样就是1002个对象,因为new String也是一个对象。
String gh = "a"; //这样就是1001个对象,双引号字符串"a"是一个string对象。//内存中有1001个对象,gh就指向了循环最后的那个对象。
for (int i = 0; i < 1000; i++) {
gh = gh + i;
}
System.out.println(gh); //这种做法非常浪费空间,不可取。要避免字符串叠加这样的代码
stringbuilder – 记一个:builder线程不安全,效率高
关于append方法可以看博客:http://blog.csdn.net/qingmengwuhen1/article/details/69399751
查看Java8中StringBuffer的append()方法改变字符串的源代码:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
str.getChars()方法即是将str的所有字符拷贝到value[]的后面,返回的还是原来的value数组。
引申:
final意义:最终的,不可改变的。
1、修饰变量,为常量,值不可变;
2、修饰对象,值可变,引用不变; —– 注意这里。。。。
3、修饰方法,方法不可重写;
4、修饰类,无子类,不可以被继承,更不可能被重写。
/**
* 测试可变字符序列。所谓可变:就是stringbuilder的char[] value是可变的。它没有final,而是默认的,同包可以访问。
StringBuilder(线程不安全,效率高),StringBuffer和StringBuilder代码差不多(线程安全,效率低)
一般作为局部变量,一般使用StringBuilder,用的多
* String:不可变字符序列
* StringBuilder 和 StringBuffer都是继承自 AbstractStringBuilder。 很相似
*
*/
public class Test01 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder(); //构造器什么都没传,但是实际上创建了一个长度为16的字符数组,看源码就知道了,
StringBuilder sb1 = new StringBuilder(32); //传32,那数组初始化长度就是32,不传就是16
StringBuilder sb2 = new StringBuilder("abcd"); 4+16=20了 //字符数组长度初始为16, value[]={'a','b','c','d',\u0000,\u0000...} \u0000是插入的默认值
sb2.append("efg");//append就是把源string的字符数组拷贝到一个新的字符数组(就是stringbuilder里的char[] value )
sb2.append(true).append(321).append("随便"); //通过return this实现方法链.
System.out.println(sb2);
System.out.println("##################");
StringBuilder gh = new StringBuilder("a"); //数组初始化的时候长度是17
for (int i = 0; i < 1000; i++) {
//这里总共就两个对象(a一个对象,new StringBuilder一个对象)
//这里循环遍历修改对象,就是数组char[] value,修改的始终是同一个对象
gh.append(i); //初始化数组长度是17,那这么append1000次,超过了17怎么办? 它会扩容,扩容就是创建一个新数组 原来的长度乘以2再加2
}
System.out.println(gh);
}
}
/**
* 测试StringBuilder的一些常用方法
其实StringBuilder和StringBuffer说白了,就是数组的练习。char[] value;
* @author dell
*
*/
public class Test02 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("abcdefghijklmnopqrstuvwxyz");
sb.delete(3, 5).delete(3, 5); //结果:abchijklmnopqrstuvwxyz 3,5删除索引3和4的字符
System.out.println(sb);
sb.reverse();
System.out.println(sb);
//用法和StringBuilder一模一样,区别就是它的方法都加了synchronized,同步表示线程安全,
//正因为安全,同步调用需要等待,效率就低了。 不加synchronized,调用不需要等待,所以效率高
StringBuffer sb2 = new StringBuffer();
}
}
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
System.out.println(s3 == s1 + s2); //false。这里是s1+s2又会生成一个对象。
System.out.println(s3.equals(s1 + s2)); //true
System.out.println(s3=="hello"+"world"); // 这个是true。这里内存里还是指向常量池。
System.out.println(s3.equals("hello" + "world")); //true
基础类型包装类
注意和string的互转。String.valueOf和 Integer.parseInt
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
Integer重写了equals
public Integer(int var1) {
this.value = var1; //所谓包装类,就是把int的值包装起来了,放到value中
}
public boolean equals(Object var1) {
if (var1 instanceof Integer) {
return this.value == ((Integer)var1).intValue();
} else {
return false;
}
}
装箱拆箱 —- 就是编译器帮你做的事儿
为什么需要? 因为自己转来转去太麻烦了。。。。
编译器自动地帮你做了转型。
缓存问题:
为什么这个范围的会当作基本类型处理? 因为这个范围的数字使用的视频非常的高,为了提高效率,基本类型再怎么也比对象快
下面这个小程序,d3和d4就不是两个对象了,所以d3==d4 是true
关于缓存,下面这个小例子说明的还不多。看看下面这个博客:
《 一道面试题关于Integer的缓存范围(-128~127)所引起的一系列问题记录》
https://blog.csdn.net/BeauXie/article/details/53013946
内部类高琪
就理解为,外部类的一个成员,和外部类的成员是平级的。
为什么需要内部类,就是2点:1、内部类可以方便的使用外部类的成员 2、内部类的存在就是为了辅助外部类,完成某些功能。
静态内部类中:不能调用外部类的普通成员,只能调用静态成员(也就是静态属性和静态方法)。 好理解吧
不相关的类中,可以直接创建。下图第三点
package com.nestedClass;
import com.nestedClass.Outer02.StaticInnerClass;
public class Demo02 {
public static void main(String[] args){
Outer02.StaticInnerClass osic = new Outer02.StaticInnerClass();
//这种等价于上面,只是引入的时候不一样。引入的时候把com.nestedClass.Outer02看出是包名即可。。
//可以直接创建
// - StaticInnerClass staticInnerClass = new StaticInnerClass();
}
}
class Outer02{
int c = 5;
static int d = 10;
public static class StaticInnerClass{
int a = 3;
static int b = 5;
public void test(){
System.out.println(d);
}
}
}
普通内部类: (和普通内部类的区别就是它依赖于外部类的对象,对象。它就不能直接创建了。。。)
public class Demo03 {
public static void main(String[] args){
//要这么用才行,不能直接InnerClass innerClass = new InnerClass();
Outer03.InnerClass innerClass = new Outer03().new InnerClass();
// InnerClass innerClass = new InnerClass(); 报错。。
}
}
class Outer03{
private int c = 5;
static int d = 10;
/*static */void ttt(){
InnerClass innerClass = new InnerClass(); //因为InnerClass是属于外部对象的,所以这里加static报错。。。
}
class InnerClass{
int a = 3;
// final static int b = 5; //成员内部类不能有静态成员,除非声明为final,并且只能是编译器可以确定值的常量表达式
// final static Date date = new Date(); 这种也不行,因为new是运行期间才确定的。
public void test(){
System.out.println(d); //成员内部类可以访问外部类所有的成员。。
}
}
}
匿名内部类:只使用一次的话才适合使用,一般用在方法里。因为匿名,没有名字,下次再用的话,就没有一个引用可以用。
关于静态内部类,和static。可以参考一篇博客:
《[面试经验]一个草根程序员如何进入BAT》写的很好。。
http://mp.weixin.qq.com/s?__biz=MzAxNzMwOTQ0NA%3D%3D&mid=2653354762&idx=5&sn=d5495b57cd953853f6108fdd7d771a78&chksm=8035d161b74258778a8a4bde7f3594a93b9a5cdb86b2dcc8ce797d0bada6e2808357f20b4710
静态内部类不依赖于外部类的实例存在,因此只需要直接创建内部类的实例就可以了,所以只有一个new关键字
static收集
1、static变量在JAVA中是属于类的,它在所有的实例中的值都是一样的。当类被JAVA虚拟机加载的时候 ,会对static变量进行初始化。
2、