一 基础知识
JAVA数据类型
3 不同类型数据的赋值
Java 中的所有浮点类型默认情况下都是 double。 不能将 double 类型的值赋给 float 类型的变量,即便该 double 类型的值处于 float 类型的范围内也是不可以的。总之,能否成功赋值取决于等号右边的值类型与等号左边的变量类型是否一致。
Variable.java
public class Variable
{
public static void main(String[] args)
{
float a;
a = (float)1.2;
//a = 1.2f;
System.out.println(a);
}
}
4 运算符 Operator
pulic class Variable3
{
pulic static void main(String[] args)
{
int a = 1;
int b = 2;
}
}
- 花括号换行,成对拼写
- 等号左右留空格,增强可读性
5 运算符续 Operator cont.
逻辑运算符的短路特性
- 逻辑与
如果第一个操作数为 false,那么结果肯定就是 false,所以在这种情况下,将不会执行逻辑与后面的运算了,即发生了短路。 - 逻辑或
如果第一个操作数为 true,那么结果肯定就是 true,所在在这种情况下,将不会执行逻辑或后面的运算了,即发生了短路。
二 面向对象程序设计
8 理解面向对象程序设计
类
类是一种抽象的概念, 类中包含了数据 (通常使用名词来表示) 与对数据的操纵 (通常使用动词来表示) 。 比如说人就是一种抽象的概念人具有姓名、年龄、身高等数据,还有吃饭、跑步等操纵数据的动作。
对象
对象是一种具体的概念,是类的一种具体表示方式。比如说人是一个类,而张三、李四、王五等具体的人就是对象。
类的内容
- 数据,数据在类中称作属性(Property 或者 Attribute)或者叫成员变量(Member variable) 。
- 对数据的操纵,这种操纵在类中称作方法(method) 。
9 面向对象之封装
面向对象程序设计的三大基本特征
继承(Inheritence) 、封装(Encapsulation) 、多态(Polymorphism)
封装
将数据与方法放在一个类中就构成了封装。
类
修饰符 class 类的名字
{
//类的内容(包含了属性与方法)
}
- 如果一个java源文件中定义了多个分类,那么这些类中最多只能有一个类是public的,换句话说,定义的多个类可以都不是public的。
- main方法一定要放在public类中(该文件中有public类的话 )
public class ParamTest
{
public void changePoint(Point point)
{
point.x = 3;
point.y = 4;
}
public static void main(String[] args)
{
ParamTest pt = new ParamTest();
Point point = new Point();
pt.changePoint(point);
System.out.println(point.x);
System.out.println(point.y);
System.out.println('a');
}
}
class Point
{
int x;
int y;
}
方法
- 如何定义
修饰符 返回类型 方法名称([参数 1, 参数 2, 参数 3…])
{
//方法体
}
- Main
main 方法是整个 Java 程序的入口点,如果类的定义没有 main 方法,则程序无法执行。 - 方法调用
通过对象来完成
对象变量.方法名([参数值 1, 参数值 2, 参数值 3….]);
- return
如果方法不返回值,那么声明方法的时候使用 void 关键字,在方法定义中可以有两种情况实现不返回值:
- 不使用 return 语句。
- 使用 return,但 return 后面没有任何值或者变量, return 后面只有一个分号,表示退出方法,返回到方法的调用端。
使用方式:return;
public void method(int a)
{
if(a < 10)
{
return;
}
System.out.println("welcome");
}
对象
通过类来生成对象(通常使用 new 关键字来生成对象)
public class Person
{
}
类名 变量名 = new 类名();
Person person = new Person();
Person person2 = new Person();
Person person3 = new Person();
new Person()即是对象
10 面向对象之封装 续
- 类中的属性又叫做成员变量(member variable) ,属性用英文表示为 property 或者attribute。
- 对象(Object)又叫做实例(Instance)。生成一个对象的过程又叫做实例化。
命名约定
- 类
首字母大写,如果一个类名由多个单词构成,那么每个单词的首字母都大写,中间不使用任何的连接符。比如 Person 类,MemberTest 类。 - 方法
首字母小写。如果一个方法由多个单词构成,那么第一个单词的所有字母全都小写,从第二个单词开始,每个单词的首字母大写。比如add,addThreeInt。 - 属性
命名约定与方法相同。比如 age,ageOfPerson。
属性
属性需要定义在类中,又叫做成员变量;而定义在方法中的变量叫做局部变量。
局部变量使用前必须要声明并赋初值;成员变量使用前必须要声明,但可以不赋初值。
1. 无论是成员变量还是局部变量,使用前都需要声明(定义) 。
2. 对于局部变量来说,使用前必须要初始化;对于成员变量来说,使用前可以不初始化。如果没有初始化成员变量就开始使用,那么每个类型的成员变量都有一个默认的初始值
- 如何定义
public class Person
{
修饰符 类型 属性名称;
}
- 如何使用属性
与方法一样,使用.运算符。首先需要生成类的实例,然后使用实例+” . ”
的方式来使用属性。
Person person = new Person();
person.age
- 引用类型
引用类型是用在对象上的。如Person person = new Person();
一个对象可以被多个引用所指向,但同一时刻,每个引用只能指向唯一的一个对象。如果一个对象被多个引用所指向,那么无论哪个引用对对象的属性进行了修改,都会反映到其他的引用当中。
public class People
{
int age = 20;
public void change(People people)
{
people = new People();
people.age = 30;
}
public static void main(String[] args)
{
People people = new People();
int age = people.age;
System.out.println(age);
people.change(people);
int age2 = people.age;
System.out.println(age2);
}
}
20
20
- 每次new People()都是新的对象(在内存的不同区域)
- main方法中的people引用的对象是(20)
- 在change方法的调用中change方法新的引用people指向了新的对象(20)并赋值30
- 回到main方法中main方法的引用仍然指向对象(20)
- main方法的people和调用change方法时生成的people是两个不同的引用且指向了两个不同的对象
11 面向对象之封装 续二
方法参数传递
对于 Java 中的方法参数传递,无论传递的是原生数据类型还是引用类型,统一是传值(pass by value) 。传完之后他是他,我是我,形式参数作为替身/影子复制了一份。(绕不过来了可以让形参换个名字)
引用类型是地址
构造方法
构造方法用于完成对象属性的初始化工作
- 特点
- 构造方法的名字必须与类名完全一致(包含大小写)
- 构造方法没有返回值,连 void 也不能出现。
- 如果在定义一个类的时候,没有为类声明构造方法,那么 Java 编译器会自动为类添加一个没有参数且方法体为空的构造方法(默认的构造方法)
- 如果在定义一个类的时候,为类声明了构造方法,那么 Java 编译器就不会再为类添加构造方法了。
- 不能显式调用类的构造方法,构造方法通常是通过 new 关键字隐式调用。
- 默认的构造方法
构造方法没有参数且方法体为空。
new
- new 关键字在生成对象时完成了三件事情:
- 为对象开辟内存空间。
- 调用类的构造方法。
- 将生成的对象的地址返回。
- 使用 new 来生成对象
- 后面的小括号()表示构造方法的参数列表;
- 如果构造方法不接收参数,那么小括号中的内容为空;
- 如果构造方法接收参数,那么小括号中的实际参数就需要与构造方法定义中的形式参数保持一致 (参数数量一致、参数类型一致、按照顺序逐一赋值) 。
16 方法重载剖析
表示两个或多个方法名字相同,但方法参数不同。
1. 参数个数不同
2. 参数类型不同 方法的返回值对重载没有任何影响
public class OverloadTest
{
public int add(int a,int b)
{
return a + b;
}
public int add(int a,int b,int c)
{
return a + b + c;
}
public static void main(String[] args)
{
OverloadTest test = new OverloadTest();
int result = test.add(1, 2);
int result2 = test.add(1, 2, 3);
System.out.println(result);
System.out.println(result2);
}
}
17 继承剖析
构造方法重载:看参数
this
如果想在一个构造方法里,调用另一个构造方法可以使用this()
。
this必须放在构造方法的第一行。
继承
JAVA是单继承的,只能从一个类(父类/基类)继承
当生成子类对象时JAVA默认首先调用父类不带参数的构造方法,生成父类对象,再调用子类的构造方法生成子类对象。
public class Child extends Parent
{
public Child()
{
System.out.println("child");
}
public static void main(String[] args)
{
Child child = new Child();
}
}
class Parent
{
public Parent(int i)
{
System.out.println("parent");
}
public Parent()//子类调用此重载的(不带参数)的构造方法
{
System.out.println("parent");
}
}
- 关于继承的3点
- 父类有的,子类也有
- 父类没有的,子类可以增加
- 父类有的,子类可以改变
- 注意事项
- 构造方法不能被继承
- 方法和属性可以被继承
- 子类的构造方法隐式的调用父类不带参数的构造方法,否则用
super()
super
子类使用super()显式调用父类的某个构造方法。
必须是构造方法的一行语句(保证先生成父类对象,再生成子类对象)。
18 多态剖析
方法重写(区别于方法重载)
子类与父类的方法返回类型一样,方法名称一样,参数一样,则子类与父类的方法构成了重写的关系。
public class InheritenceTest2
{
public static void main(String[] args)
{
Dog dog = new Dog();
dog.run();
}
}
class Animal
{
public void run()
{
System.out.println("animal is running");
}
}
class Dog extends Animal
{
public void run()
{
//super.run();
System.out.println("dog is running");
}
}
dog is running
重载是一个类中内部的两个或多个方法,重写是子类和父类中的方法。
可以用super.run()调用父类的方法,不用一定放在第一行
多态
父类型的引用可以指向子类型的对象
Flower rose = new Rose();
19 多态详解
Parent p = new Child();
当使用多态方式调用方法时,首先检查父类中是否有sing()方法,如果没有则编译错误;如果有,再去调用子类的 sing()方法。
- 一共有两种类型的强制类型转换:
- 向上类型转换(upcast):比如说将 Cat 类型转换为 Animal 类型,即将子类型转换为父类型。对于向上类型转换,不需要显式指定。
- 向下类型转换(downcast):比如将 Animal 类型转换为 Cat 类型。即将父类型转换为子类型。对于向下类型转换,必须要显式指定(必须要使用强制类型转换)
Cat cat = new Cat();
Animal animal = cat;//向上类型转换
animal.sing();
Animal a = new Cat();
Cat c = (Cat)a;//向下类型转换
c.sing();
20 多态详解
一个体现多态特点的例子:
1. BMW等重写了父类car的run方法
2. 父类run方法的car的引用类型变量分别指向不同的子类对象(多态的定义),进而被调用
public class PolyTest5
{
/*
public void run(BMW bmw)
{
bmw.run();
}
public void run(QQ qq)
{
qq.run();
}
*/
public void run(Car car)
{
car.run();
}
public static void main(String[] args)
{
/*
PolyTest5 test = new PolyTest5();
BMW bmw = new BMW();
test.run(bmw);
QQ qq = new QQ();
test.run(qq);
*/
PolyTest5 test = new PolyTest5();
Car car = new BMW();
test.run(car);
QQ qq = new QQ();
test.run(qq);
}
}
class Car
{
public void run()
{
System.out.println("car is running");
}
}
class BMW extends Car
{
public void run()
{
System.out.println("BMW is running");
}
}
class QQ extends Car
{
public void run()
{
System.out.println("QQ is running");
}
}
21 多态详解 续
抽象类
抽象类(abstract class):使用了 abstract 关键字所修饰的 类叫做抽象类。抽
象类无法实例化,也就是说,不能 new 出来一个抽象类的对象(实例)。
抽象方法(abstract method):使用 abstract 关键字所修饰的方法叫做抽象方法。抽象方法需要定义在抽象类中。相对于抽象方法,之前所定义的方法叫做具体方法(有声明,有实现{}
)。
一种约束或者规范,由子类决定如何实现。
- 如果一个类包含了抽象方法,那么这个类一定是抽象类。
- 如果某个类是抽象类,那么该类可以包含具体方法(有声明、有实现)。
- 如果一个类中包含了抽象方法,那么这个类一定要声明成 abstract class,也就是说,该类一定是抽象类;反之,如果某个类是抽象类,那么该类既可以包含抽象方法,也可以包含具体方法。
- 无论何种情况,只要一个类是抽象类,那么这个类就无法实例化。
- 在子类继承父类(父类是个抽象类)的情况下,那么该子类必须要实现父类中所定义的所有抽象方法;否则,该子类需要声明成一个 abstract class。
//5.
public class Test
{
public static void main(String[] args)
{
//R r = new R();
}
}
abstract class T
{
public abstract void method();
public void test()
{
System.out.println("test");
}
}
class R extends T
{
public void method()
{
System.out.println("method");
}
}
22 static与final关键字详解
接口
- 接口中的所有方法都是抽象方法
- abstract关键字可以省略
- 可以将接口看作是特殊的抽象类(接口中不能有具体方法)
- 类可以实现接口,implements关键字
- 一个类实现了某个接口,那么该类必须要实现该接口中声明的所有方法,如果该类是抽象类,就不必都实现了。
- 一个类可以实现多个接口
public class Test3
{
public static void main(String[] args)
{
MyClass myClass = new MyClass();
myClass.output();
myClass.output2();
myClass.output3();
}
}
interface MyInterface{
public void output();
}
interface MyInterface2
{
public void output2();
}
class MyParent
{
public void output3()
{
System.out.println("output3");
}
}
class MyClass extends MyParent implements MyInterface,MyInterface2
{
public void output()
{
System.out.println("output");
}
public void output2()
{
System.out.println("output");
}
}
- 所谓多态,就是父类型的引用可以指向子类型的对象,或者接口类型的引用可以指向实现该接口的类的实例。(子类继承了父类,子类实现了接口)
- 接口与实现接口的类之间的强制类型转换方式参照父类和子类方式
static关键字
用于
* 修饰属性
无论一个类生成了多少个对象这些对象共同使用唯一一份静态的成员变量
如果一个成员变量是static的,那么我们可以通过类名.成员变量名的方式来使用它,MyStatic.a
public class StaticTest
{
public static void main(String[] args)
{
/*
MyStatic myStatic = new MyStatic();
MyStatic myStatic2 = new MyStatic();
myStatic.a = 10;
System.out.println(myStatic2.a);
*/
MyStatic myStatic = new MyStatic();
MyStatic.a = 10;
System.out.println(myStatic.a);
}
}
class MyStatic
{
static int a;
}
10,10
23 static与final使用陷阱
static 修饰方法(静态方法)
可以使用类名.方法名的方式访问
- 静态方法只能继承,不能重写(Override)。(实际上是把父方法隐藏掉了)
final修饰类
该类是个终态类,不能被继承
final修饰方法
该方法是终态方法,不能被重写(override)
final修饰属性
该属性是常量,不能被改写
当final修饰一个原生数据类型时,表示该原生数据类型的值不能发生变化(比如说不能从 10 变为 20);如果 final 修饰一个引用类型时,表示该引用类型不能再指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。
24 static与final使用陷阱 续
- 对于 final 类型成员变量,一般来说有两种赋初值方式:
a) 在声明 final 类型的成员变量时就赋上初值。
b) 在声明 final 类型的成员变量时不赋初值,但在类的所有构造方法中都为其赋上初值 。
static 代码块
- static 代码块:静态代码块。 静态代码块的作用也是完成一些初始化工作。首先执行静态代码块,然后执行构造方法。静态代码块在类被加载的时候执行,而构造方法是在生成对象的时候执行;要想调用某个类来生成对象,首先需要将类加载到 Java 虚拟机上(JVM),然后由 JVM 加载这个类来生成对象。
类的静态代码块只会执行一次,是在类被加载的时候执行的,因为每个类只会被加载一次,所以静态代码块也只会被执行一次;而构造方法则不然,每次生成一个对象的时候都会调用类的构造方法,所以 new 一次就会调用构造方法一次。
不能在静态方法中访问非静态成员变量;可以在静态方法中访问静态的成员
变量。可以在非静态方法中访问静态的成员变量。- 总结:静态的只能访问静态的;非静态的可以访问一切。
- 不能在静态方法中使用 this 关键字。
25 单例模式详解
接口
- 接口中所声明的方法都是抽象方法。接口中的方法都是 public 的。
- 接口中也可以定义成员变量。接口中的成员变量都是 public、final、static 的。
设计模式
单例模式(Singleton) :表示一个类只会生成唯一的一个对象。
public class SingletonTest
{
public static void main(String[] args)
{
Singleton singleton = Singleton.getSingleton();
Singleton singleton2 = Singleton.getSingleton();
System.out.println(singleton == singleton2);
}
}
class Singleton
{
private static Singleton singleton = new Singleton();
private Singleton()
{
}
public static Singleton getSingleton()
{
return singleton;
}
}
26 包与导入例句剖析
包
- 用于将完成不同功能的类分门别类,放在不同的目录(包)下。
- 包的命名规则:将公司域名反转作为包名。www.shengsiyuan.com, com.shengsiyuan (包名) ,对于包名:每个字母都需要小写。如果定义类的时候没有使用 package,那么Java 就认为我们所定义的类位于默认包里面(default package)。
- 使用编译参数 –d,方式为
javac –d . 源文件.java
,这样在编译后,编译器会自动帮助我们建立好包所对应的目录结构。
import
- import 的语法:import com.shengsiyuan.PackageTest;
- import com.shengsiyuan.*,表示导入 com.shengsiyuan 包下面的所有类。
- 关于 package、import、class 的顺序问题:
a) 首先需要定义包(package),可选
b) 接下来使用 import 进行导入,可选
c) 然后才是 class 或 interface 的定义。 - 如果两个类在同一个包下面,那么则不需要导入,直接使用即可。
27 访问控制符详解
访问修饰符(access modifier) 。
1) public(公共的) :被 public 所修饰的属性和方法可以被所有类访问。
2) protected (受保护的): 被 protected 所修饰的属性和方法可以在类内部、相同包
以及该类的子类所访问。
3) private(私有的):被 private 所修饰的属性和方法只能在该类内部使用
4) 默认的(不加任何访问修饰符):在类内部以及相同包下面的类所使用。
private(仅类内部) 默认(+相同包) protected(+子类) public
28 Object类详解
java.lang.Object
当打印引用时,实际上会打印出引用所指对象的 toString()方法的返回值,因为每个类都直接或间接地继承自 Object,而 Object 类中定义了 toString(),因此每个类都有toString()这个方法。
28 String类源代码深析
29 String类源代码深析
equals()方法
- 该方法定义在 Object 类当中,因此 Java 中的每个类都具有该方法,对于 Object 类的 equals()方法来说,它是判断调用 equals()方法的引用与传进来的引用是否一致,即这两个引用是否指向的是同一个对象。对于 Object 类的 equals()方法来说,它等价于==。
- 对于 String 类的 equals()方法来说,它是判断当前字符串与传进来的字符串的内容是否一致。
- 对于 String 对象的相等性判断来说,请使用 equals()方法,而不要使用==。
- String 是常量,其对象一旦创建完毕就无法改变。当使用+拼接字符串时,会生成新的 String 对象,而不是向原有的 String 对象追加内容。
String Pool(字符串池)
- 查找 String Pool 中是否存在“aaa”这个对象,如果不存在,则在 String Pool 中创建一个“aaa”对象,然后将 String Pool 中的这个“aaa”对象的地址返回来,赋给引用变量 s,这样 s 会指向 String Pool 中的这个“aaa”字符串对象.
如果存在,则不创建任何对象,直接将 String Pool 中的这个“aaa”对象地址返回来,赋给 s 引用。
- String s = new String(“aaa”)
- 首先在 String Pool 中查找有没有“aaa”这个字符串对象,如果有,则不在 String Pool
中再去创建“aaa”这个对象了,直接在堆中(heap)中创建一个“aaa”字符串对
象,然后将堆中的这个“aaa”对象的地址返回来,赋给 s 引用,导致 s 指向了堆中
创建的这个“aaa”字符串对象。 - 如果没有,则首先在 String Pool 中创建一个“aaa“对象,然后再在堆中(heap)创
建一个”aaa“对象,然后将堆中的这个”aaa“对象的地址返回来,赋给 s 引用,
导致 s 指向了堆中所创建的这个”aaa“对象。
30 Java数组设置剖析
包装类(Wrapper Class)
针对于原生数据类型的包装。所有的包装类(8 个)都位于java.lang 包下。Java 中的 8 个包装类分别是:Byte, Short, Integer, Long, Float, Double, Character, Boolean。他们的使用方式都是一样的,可以实现原生数据类型与包装类型的双向转换。
数组(Array)
- 相同类型数据的集合就叫做数组。
- 如何定义数组。 type[] 变量名 = new type[数组中元素的个数];可以按照下列方式定义长度为 10 的数组:
int[] a = new int[10]; 或者 int a[] = new int[10]; - 数组中的元素索引是从 0 开始的。对于数组来说,最大的索引==数组的长度 – 1。
- 定义数组的第 3 种方式: type[] 变量名 = {new type[]}{逗号分隔的初始化值列表};
int[] b = new int[]{1,2,3};
int[] b = {1,2,3};
- 二维数组。二维数组是一种平面的二维结构,本质上是数组的数组。二维数组的定义方
式:type[][] a = new type[2][3];
int[][] a = new int [][]{{1,2,3},{4},{5,6,7}}
- 三维数组。
type[][][] a = new type[2][3][4];
38 IDE详解
对于 Java 中的常量的命名规则:所有单词的字母都是大写,如果有多个单词,那么使用下划线连接即可。比如说:public static final int AGE_0F_PERSON = 20;
在 Java 中声明 final 常量时通常都会加上 static 关键字,这样对象的每个实例都会访问唯一一份常量值。
41 linkedList源代码深入剖析
ArrayList 集合
- 集合中存放的依然是对象的引用而不是对象本身。
- ArrayList 底层采用数组实现,当使用不带参数的构造方法生成 ArrayList 对象
时,实际上会在底层生成一个长度为 10 的 Object 类型数组 - 如果增加的元素个数超过了 10 个,那么 ArrayList 底层会新生成一个数组,长
度为原数组的 1.5 倍+1,然后将原数组的内容复制到新数组当中,并且后续
增加的内容都会放到新数组当中。当新数组无法容纳增加的元素时,重复该
过程。 - 对于 ArrayList 元素的删除操作,需要将被删除元素的后续元素向前移动,代
价比较高。 - 集合当中只能放置对象的引用,无法放置原生数据类型,我们需要使用原生
数据类型的包装类才能加入到集合当中。 - 集合当中放置的都是 Object 类型,因此取出来的也是 Object 类型,那么必须
要使用强制类型转换将其转换为真正的类型(放置进去的类型)。 - 关于 ArrayList 与 LinkedList 的比较分析
a) ArrayList 底层采用数组实现,LinkedList 底层采用双向链表实现。
b) 当执行插入或者删除操作时,采用 LinkedList 比较好。
c) 当执行搜索操作时,采用 ArrayList 比较好。
策略模式
- 策略模式的组成
– 抽象策略角色:策略类,通常由一个接口或者
抽象类实现(comparator)
– 具体策略角色:包装了相关的算法和行为
– 环境角色:持有一个策略类的引用,最终给客
户端调用的。(TreeSet/TreeMap集合类) - 策略模式的编写步骤
– 1.对策略对象定义一个公共接口。
– 2.编写策略类,该类实现了上面的公共接口
– 3.在使用策略对象的类中保存一个对策略对
象的引用。
– 4.在使用策略对象的类中,实现对策略对象
的set和get方法(注入)或者使用构造方法完
成赋值
52 HashSet与HashMap源代码深度剖析
- HashSet 底层是使用 HashMap 实现的。当使用 add 方法将对象添加到 Set 当中时,
实际上是将该对象作为底层所维护的 Map 对象的 key,而 value 则都是同一个 Object
对象(该对象我们用不上) ; - HashMap 底层维护一个数组,我们向 HashMap 中所放置的对象实际上是存储在该数
组当中; - 当向 HashMap 中 put 一对键值时,它会根据 key 的 hashCode 值计算出一个位置,
该位置就是此对象准备往数组中存放的位置。
53 泛型详解
55 泛型高级进阶
限制泛型可用类型
- 在定义泛型类别时,预设可以使用任何的类型来实例化泛型类型中的类型,但是如果想要限制使用泛型类别时,只能用某个特定类型或者是其子类型才能实例化该类型时,可以在定义类型时,使用extends关键字指定这个类型必须是继承某个类,或者实现某个接口
ListGenericFoo.java
public class ListGeneticFoo<T extends List>
...
ListGenericFoo<ArrayList> foo1 = new ListGeneraicFoo<ArrayList>()
- 当没有指定泛型继承的类型或接口时,默认使用T extends Object,所以默认情况下任何类型都可以作为参数传入
类型通配声明
public class GenericTest<T>
...
GenericTest<? extends List> ge = null;
ge = new GenericTest<ArrayList>();
ge = new GenericTest<LinkedList>();
GenericTest<? super List> ge2 = null;
ge2 = new GenericTest<Object>();
GenericTest<String> ge3 = new GenericTest<String>();
ge3.setFoo("hello world");
GenericTest<?> ge4 = ge3;
System.out.println(ge4.getFoo());
ge4.setFoo(null);
System.out.println(ge4.getFoo());
//ge4.setFoo("welcome");
- 使用
<?>
或是<? extends SomeClass>
的声明方式,意味著您只能通过该名称來取得所参考实例的信息,或者是移除某些信息,但不能增加它的信息,因为只知道当中放置的是SomeClass的子类,但不确定是什么类的实例,编译器不让您加入信息,理由是,如果可以加入信息的話,那么您就得記得取回的实例是什么类型,然后转换为原來的类型方可进行操作,这就失去了使用泛型的意义。
57 增强的For循环与自动装箱拆箱
For循环
for(type element : array) {
System.out.println(element)....
}
自动装箱拆箱
Collection<Integer> c = new ArrayList<Integer>();
c.add(3);//将int类型的3转换为Integer类型并放到集合当中
c.add(a + 3);
Map<String, Integer> map = new HashMap<String, Integer>();
for(String word : args)
{
map.put(word, (null == map.get(word)) ? 1 : map.get(word) + 1);
}//put 自动装箱
可变参数
可变参数本质上就是一个数组,对于某个声明了可变参数的方法来说,我们既可以传递离散的值,也可以传递数组对象。但如果将方法中的参数定义为数组,那么只能传递数组对象而不能传递离散的值。
可变参数必须要作为方法参数的最后一个参数,即一个方法不可能具有两个或两个以上的可变参数。
private static int sum(String str, int... nums)
{
System.out.println(str);
int sum = 0;
for (int num : nums)
{
sum += num;
}
return sum;
}
59 类型安全的枚举
枚举
public enum Color
{
Red,White,Blue
}
values()方法
for(Color color : Color.values())
{
System.out.println(color);
}
枚举(Enum): 我们所定义的每个枚举类型都继承自 java.lang.Enum 类,枚举中的每个成员默认都是 public static final 的。
而每个枚举的成员其实就是您定义的枚举类型的一個实例(Instance) 。换句话说,当定义了一个枚举类型后,在编译时刻就能确定该枚举类型有几个实例,分别是什么。在运行期间我们无法再使用该枚举类型创建新的实例了,这些实例在编译期间就已经完全确定下来了。
一个完整的实例
public enum Coin
{
penny("hello"), nickel("world"), dime("welcome"), quarter("hello world");//括号里相当于构造方法的参数
private String value;
public String getValue()
{
return value;
}
Coin(String value)
{
this.value = value;
}
public static void main(String[] args)
{
Coin coin = Coin.quarter;
System.out.println(coin.getValue());
}
}
61 java反射机制深度剖析
反射
- Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义)并生成其对象实体、或对其fields设值、或唤起其methods。
- 在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中
– Class类:代表一个类。
– Field 类:代表类的成员变量(成员变量也称为类的属性)。
– Method类:代表类的方法。
– Constructor 类:代表类的构造方法。
– Array类:提供了动态创建数组,以及访问数组的元素的静态方法
62 Class类、Method类及Field类的使用方式深度探析
- 要想使用反射,首先需要获得待处理类或对象所对应的 Class 对象。
- 获取某个类或某个对象所对应的 Class 对象的常用的 3 种方式:
a) 使用 Class 类的静态方法 forName: Class.forName(“java.lang.String ”);
b) 使用类的.class 语法:String.class;
c) 使用对象的 getClass()方法:String s = “aa”; Class<?> clazz = s.getClass();
- 若想通过类的不带参数的构造方法来生成对象,我们有两种方式:
a) 先获得 Class 对象,然后通过该 Class 对象的 newInstance()方法直接生成即可:
Class<?> classType = String.class;
Object obj = classType.newInstance();
b) 先获得 Class 对象,然后通过该对象获得对应的 Constructor 对象,再通过该 Constructor
对象的 newInstance()方法生成:
Class<?> classType = Customer.class;
Constructor cons = classType.getConstructor(new Class[]{});
Object obj = cons.newInstance(new Object[]{}); - 若想通过类的带参数的构造方法生成对象,只能使用下面这一种方式:
Class<?> classType = Customer.class;
Constructor cons = classType.getConstructor(new Class[]{String.class, int.class});
Object obj = cons.newInstance(new Object[]{“hello” , 3}); - Integer.TYPE 返回的是 int,而 Integer .class 返回的是 Integer 类所对应的 Class 对象。
用反射实现类拷贝
package com.lee.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectTest2
{
//实现多Customer对象的拷贝
public Object copy(Object object) throws Exception
{
Class<?> classType = object.getClass();
// Constructor cons = classType.getConstructor(new Class[] {});
//
// Object obj = cons.newInstance(new Object[] {"hellow", 3});
// System.out.println(obj);
Object copyObject = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
Field[] fields = classType.getDeclaredFields();//可以获得私有的成员变量
//重建每个属性的get,set方法;通过属性找到方法
for(Field field : fields)
{
String name = field.getName();
String getMethodName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
String setMethodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
Method getMethod = classType.getMethod(getMethodName, new Class[] {});
Method setMethod = classType.getMethod(setMethodName, new Class[] {field.getType()});
//执行一下映射的set,get方法;get原始的object,set创建的copyObject
Object value = getMethod.invoke(object, new Object[]{});//invoke用方法来执行实例对应的参数
//反射方法返回值为Object类型
setMethod.invoke(copyObject, new Object[]{value});
}
return copyObject;
}
public static void main(String[] args) throws Exception
{
Customer customer = new Customer("li", 22);
customer.setId(1L);
ReflectTest2 test = new ReflectTest2();
Customer customer2 = (Customer)test.copy(customer);//注意返回类型和强制转换
System.out.println(customer2.getId() + "," + customer2.getName() + ","
+ customer2.getAge());
}
}
class Customer
{
private Long id;
private String name;
private int age;
public Customer()
{
}
public Customer(String name, int age)
{
this.name = name;
this.age = age;
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
}
98 线程的实现方式
线程的实现
有两种方式,第一种方式是继承 Thread 类,然后重写 run 方法;第二种是实现 Runnable 接口,然后实现其 run 方法。 将我们希望线程执行的代码放到 run 方法中,然后通过 start 方法来启动线程,start方法首先为线程的执行准备好系统资源,然后再去调用 run 方法。当某个类继承了Thread 类之后,该类就叫做一个线程类。1 继承Thread类并重写run方法
2 实现Runnable接口的类实现run方法
- Thread 类也实现了 Runnable 接口,因此实现了 Runnable 接口中的 run 方法;
- 当生成一个线程对象时,如果没有为其设定名字,那么线程对象的名字将使用如下形式:Thread-number ,该 number 将是自动增加的,并被所有的 Thread 对象所共享(因为它是 static 的成员变量) 。
- 当使用第一种方式来生成线程对象时,我们需要重写 run 方法,因为 Thread 类的 run方法此时什么事情也不做。
- 当使用第二种方式来生成线程对象时,我们需要实现 Runnable 接口的 run 方法,然后使用 new Thread(new MyThread()) (假如 MyThread 已经实现了 Runnable 接口) 来生成线程对象,这时的线程对象的 run 方法就会调用MyThread 类的 run 方法,这样我们自己编写的 run 方法就执行了。
停止线程
public class MyThread implements Runnable
{ private boolean flag=true;
public void run()
{ while (flag)
{…}
}
public void stopRunning()
{ flag=false;}
}
public class ControlThread
{ private Runnable r=new MyThread();
private Thread t=new Thread(r);
public void startThread()
{ t.start(); }
publi void stopThread()
{ r.stopRunning();
100 线程同步问题深度解析
线程的生命周期
- 创建状态
- 可运行状态
- 不可运行状态
- 消亡状态
线程优先级
线程的优先级及其设置
设置优先级是为了在多线程环境中便于系统对线
程的调度,优先级高的线程将优先执行。
一个线程的优先级设置遵从以下原则:
– 线程创建时,子继承父的优先级
– 线程创建后,可通过调用setPriority()方法改变优先级。
– 线程的优先级是1-10之间的正整数。
1 - MIN_PRIORITY,
10 – MAX_PRIORITY
5- NORM_PRIORITY
线程的调度策略
线程调度器选择优先级最高的线程运行。但是,如果发生以下情况,就会终止线程的运行。
• 线程体中调用了yield()方法,让出了对CPU的占用权
• 线程体中调用了sleep()方法, 使线程进入睡眠状态
• 线程由于I/O操作而受阻塞
• 另一个更高优先级的线程出现。
• 在支持时间片的系统中,该线程的时间片用完。
关于成员变量与局部变量: 如果一个变量是成员变量, 那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程) 。
如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。